src/ostree/ot-admin-builtin-diff.c \
src/ostree/ot-admin-builtin-deploy.c \
src/ostree/ot-admin-builtin-prune.c \
- src/ostree/ot-admin-builtin-pull-deploy.c \
src/ostree/ot-admin-builtin-os-init.c \
src/ostree/ot-admin-builtin-install.c \
+ src/ostree/ot-admin-builtin-status.c \
src/ostree/ot-admin-builtin-run-triggers.c \
src/ostree/ot-admin-builtin-upgrade.c \
- src/ostree/ot-admin-builtin-update-kernel.c \
src/ostree/ot-admin-builtins.h \
src/ostree/ot-admin-functions.h \
src/ostree/ot-admin-functions.c \
+ src/ostree/ot-admin-deploy.h \
+ src/ostree/ot-admin-deploy.c \
+ src/ostree/ot-bootloader.h \
+ src/ostree/ot-bootloader.c \
+ src/ostree/ot-bootloader-syslinux.h \
+ src/ostree/ot-bootloader-syslinux.c \
+ src/ostree/ot-config-parser.h \
+ src/ostree/ot-config-parser.c \
+ src/ostree/ot-deployment.h \
+ src/ostree/ot-deployment.c \
+ src/ostree/ot-ordered-hash.h \
+ src/ostree/ot-ordered-hash.c \
$(NULL)
ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libgsystem -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(srcdir)/src/ostree -DLOCALEDIR=\"$(datadir)/locale\"
# Boston, MA 02111-1307, USA.
if !TRIGGERS_ONLY
-sbin_PROGRAMS += ostree-switch-root
if BUILDOPT_DRACUT
sbin_PROGRAMS += ostree-prepare-root
sbin_PROGRAMS += ostree-remount
ostree_prepare_root_LDADD = libswitchroot-mountutil.la
ostree_prepare_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
-ostree_switch_root_SOURCES = src/switchroot/ostree-switch-root.c
-ostree_switch_root_LDADD = libswitchroot-mountutil.la
-ostree_switch_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
-
ostree_remount_SOURCES = src/switchroot/ostree-remount.c
ostree_remount_LDADD = libswitchroot-mountutil.la
ostree_remount_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot
t0005-corruption \
t0006-libarchive \
t0011-pull-archive-z \
+ t0015-admin-deploy \
$(NULL)
insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh))
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
- * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ * Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
+#include "ot-admin-deploy.h"
+#include "ot-ordered-hash.h"
#include "ostree.h"
#include <glib/gi18n.h>
-typedef struct {
- OstreeRepo *repo;
- OtAdminBuiltinOpts *admin_opts;
- GFile *ostree_dir;
- char *osname;
- GFile *osname_dir;
-
- char *current_deployment_ref;
- char *previous_deployment_ref;
- char *resolved_commit;
- char *resolved_previous_commit;
-
- char *previous_deployment_revision;
- GFile *deploy_target_path;
- GFile *previous_deployment;
-} OtAdminDeploy;
-
-static gboolean opt_no_kernel;
-static gboolean opt_force;
+static gboolean opt_no_bootloader;
+static gboolean opt_retain;
+static char **opt_kernel_argv;
+static char *opt_osname;
+static char *opt_origin_path;
static GOptionEntry options[] = {
- { "no-kernel", 0, 0, G_OPTION_ARG_NONE, &opt_no_kernel, "Don't update kernel related config (initramfs, bootloader)", NULL },
- { "force", 0, 0, G_OPTION_ARG_NONE, &opt_force, "Overwrite any existing deployment", NULL },
+ { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
+ { "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", NULL },
+ { "no-bootloader", 0, 0, G_OPTION_ARG_NONE, &opt_no_bootloader, "Don't update bootloader", NULL },
+ { "retain", 0, 0, G_OPTION_ARG_NONE, &opt_retain, "Do not delete previous deployment", NULL },
+ { "karg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv, "Set kernel argument, like --karg=root=/dev/sda1", NULL },
{ NULL }
};
-/**
- * update_current:
- *
- * Atomically swap the /ostree/current symbolic link to point to a new
- * path. If successful, the old current will be saved as
- * /ostree/previous, and /ostree/current-etc will be a link to the
- * current /etc subdirectory.
- *
- * Unless the new-current equals current, in which case, do nothing.
- */
-static gboolean
-update_current (OtAdminDeploy *self,
- GFile *current_deployment,
- GFile *deploy_target,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- ot_lobj GFile *current_path = NULL;
- ot_lobj GFile *current_etc_path = NULL;
- ot_lobj GFile *previous_path = NULL;
- ot_lobj GFile *tmp_current_path = NULL;
- ot_lobj GFile *tmp_current_etc_path = NULL;
- ot_lobj GFile *tmp_previous_path = NULL;
- ot_lobj GFileInfo *previous_info = NULL;
- ot_lfree char *relative_current = NULL;
- ot_lfree char *relative_current_etc = NULL;
- ot_lfree char *relative_previous = NULL;
-
- current_path = g_file_get_child (self->osname_dir, "current");
- current_etc_path = g_file_get_child (self->osname_dir, "current-etc");
- previous_path = g_file_get_child (self->osname_dir, "previous");
-
- relative_current = g_file_get_relative_path (self->osname_dir, deploy_target);
- g_assert (relative_current);
- relative_current_etc = g_strconcat (relative_current, "-etc", NULL);
-
- if (current_deployment)
- {
- ot_lfree char *relative_previous = NULL;
-
- if (g_file_equal (current_deployment, deploy_target))
- {
- g_print ("ostadmin: %s already points to %s\n", gs_file_get_path_cached (current_path),
- relative_current);
- return TRUE;
- }
-
- tmp_previous_path = g_file_get_child (self->osname_dir, "tmp-previous");
- (void) gs_file_unlink (tmp_previous_path, NULL, NULL);
-
- relative_previous = g_file_get_relative_path (self->osname_dir, current_deployment);
- g_assert (relative_previous);
- if (symlink (relative_previous, gs_file_get_path_cached (tmp_previous_path)) < 0)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
- }
-
- tmp_current_path = g_file_get_child (self->osname_dir, "tmp-current");
- (void) gs_file_unlink (tmp_current_path, NULL, NULL);
-
- if (symlink (relative_current, gs_file_get_path_cached (tmp_current_path)) < 0)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- tmp_current_etc_path = g_file_get_child (self->osname_dir, "tmp-current-etc");
- (void) gs_file_unlink (tmp_current_etc_path, NULL, NULL);
- if (symlink (relative_current_etc, gs_file_get_path_cached (tmp_current_etc_path)) < 0)
- {
- ot_util_set_error_from_errno (error, errno);
- goto out;
- }
-
- if (!gs_file_rename (tmp_current_path, current_path,
- cancellable, error))
- goto out;
- if (!gs_file_rename (tmp_current_etc_path, current_etc_path,
- cancellable, error))
- goto out;
-
- if (tmp_previous_path)
- {
- if (!gs_file_rename (tmp_previous_path, previous_path,
- cancellable, error))
- goto out;
- }
-
- g_print ("ostadmin: %s set to %s\n", gs_file_get_path_cached (current_path),
- relative_current);
-
- ret = TRUE;
- out:
- return ret;
-}
-
-typedef struct {
- GError **error;
- gboolean caught_error;
-
- GMainLoop *loop;
-} ProcessOneCheckoutData;
-
-static void
-on_checkout_complete (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
-{
- ProcessOneCheckoutData *data = user_data;
- GError *local_error = NULL;
-
- if (!ostree_repo_checkout_tree_finish ((OstreeRepo*)object, result,
- &local_error))
- goto out;
-
- out:
- if (local_error)
- {
- data->caught_error = TRUE;
- g_propagate_error (data->error, local_error);
- }
- g_main_loop_quit (data->loop);
-}
-
-
-/**
- * ensure_unlinked:
- *
- * Like gs_file_unlink(), but return successfully if the file doesn't
- * exist.
- */
-static gboolean
-ensure_unlinked (GFile *path,
- GCancellable *cancellable,
- GError **error)
+gboolean
+ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
{
gboolean ret = FALSE;
- GError *temp_error = NULL;
-
- if (!gs_file_unlink (path, cancellable, &temp_error))
- {
- if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
- {
- g_clear_error (&temp_error);
- }
- else
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- }
-
- ret = TRUE;
- out:
- return ret;
-}
+ __attribute__((unused)) GCancellable *cancellable = NULL;
+ const char *refspec;
+ GOptionContext *context;
+ GFile *sysroot = admin_opts->sysroot;
+ GKeyFile *origin = NULL;
+ int current_bootversion;
+ int new_bootversion;
+ gs_unref_object OstreeRepo *repo = NULL;
+ gs_unref_ptrarray GPtrArray *current_deployments = NULL;
+ gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+ gs_unref_object OtDeployment *new_deployment = NULL;
+ gs_unref_object OtDeployment *booted_deployment = NULL;
+ gs_free char *revision = NULL;
+
+ context = g_option_context_new ("REFSPEC - Checkout revision REFSPEC as the new default deployment");
-/**
- * copy_one_config_file:
- *
- * Copy @file from @modified_etc to @new_etc, overwriting any existing
- * file there.
- */
-static gboolean
-copy_one_config_file (OtAdminDeploy *self,
- GFile *orig_etc,
- GFile *modified_etc,
- GFile *new_etc,
- GFile *src,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- ot_lobj GFileInfo *src_info = NULL;
- ot_lobj GFile *dest = NULL;
- ot_lobj GFile *parent = NULL;
- ot_lfree char *relative_path = NULL;
-
- relative_path = g_file_get_relative_path (modified_etc, src);
- g_assert (relative_path);
- dest = g_file_resolve_relative_path (new_etc, relative_path);
+ g_option_context_add_main_entries (context, options, NULL);
- src_info = g_file_query_info (src, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
- if (!src_info)
+ if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
- if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_DIRECTORY)
- {
- ot_lobj GFileEnumerator *src_enum = NULL;
- ot_lobj GFileInfo *child_info = NULL;
- GError *temp_error = NULL;
-
- /* FIXME actually we need to copy permissions and xattrs */
- if (!gs_file_ensure_directory (dest, TRUE, cancellable, error))
- goto out;
-
- src_enum = g_file_enumerate_children (src, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
-
- while ((child_info = g_file_enumerator_next_file (src_enum, cancellable, error)) != NULL)
- {
- ot_lobj GFile *child = g_file_get_child (src, g_file_info_get_name (child_info));
-
- if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, child,
- cancellable, error))
- goto out;
- }
- g_clear_object (&child_info);
- if (temp_error != NULL)
- {
- g_propagate_error (error, temp_error);
- goto out;
- }
- }
- else
- {
- parent = g_file_get_parent (dest);
-
- /* FIXME actually we need to copy permissions and xattrs */
- if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
- goto out;
-
- if (!g_file_copy (src, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
- cancellable, NULL, NULL, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-/**
- * merge_etc_changes:
- *
- * Compute the difference between @orig_etc and @modified_etc,
- * and apply that to @new_etc.
- *
- * The algorithm for computing the difference is pretty simple; it's
- * approximately equivalent to "diff -unR orig_etc modified_etc",
- * except that rather than attempting a 3-way merge if a file is also
- * changed in @new_etc, the modified version always wins.
- */
-static gboolean
-merge_etc_changes (OtAdminDeploy *self,
- GFile *orig_etc,
- GFile *modified_etc,
- GFile *new_etc,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- ot_lobj GFile *ostree_etc = NULL;
- ot_lobj GFile *tmp_etc = NULL;
- ot_lptrarray GPtrArray *modified = NULL;
- ot_lptrarray GPtrArray *removed = NULL;
- ot_lptrarray GPtrArray *added = NULL;
- guint i;
-
- modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
- removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
- added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
-
- if (!ostree_diff_dirs (orig_etc, modified_etc, modified, removed, added,
- cancellable, error))
- {
- g_prefix_error (error, "While computing configuration diff: ");
- goto out;
- }
-
- if (modified->len > 0 || removed->len > 0 || added->len > 0)
- g_print ("ostadmin: Processing config: %u modified, %u removed, %u added\n",
- modified->len,
- removed->len,
- added->len);
- else
- g_print ("ostadmin: No modified configuration\n");
-
- for (i = 0; i < removed->len; i++)
- {
- GFile *file = removed->pdata[i];
- ot_lobj GFile *target_file = NULL;
- ot_lfree char *path = NULL;
-
- path = g_file_get_relative_path (orig_etc, file);
- g_assert (path);
- target_file = g_file_resolve_relative_path (new_etc, path);
-
- if (!ensure_unlinked (target_file, cancellable, error))
- goto out;
- }
-
- for (i = 0; i < modified->len; i++)
- {
- OstreeDiffItem *diff = modified->pdata[i];
-
- if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, diff->target,
- cancellable, error))
- goto out;
- }
- for (i = 0; i < added->len; i++)
- {
- GFile *file = added->pdata[i];
-
- if (!copy_one_config_file (self, orig_etc, modified_etc, new_etc, file,
- cancellable, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-/**
- * deploy_tree:
- *
- * Look up @revision in the repository, and check it out in
- * OSTREE_DIR/deploy/OS/DEPLOY_TARGET.
- *
- * Merge configuration changes from the old deployment, if any.
- *
- * Update the OSTREE_DIR/current{,-etc} and OSTREE_DIR/previous symbolic
- * links.
- */
-static gboolean
-deploy_tree (OtAdminDeploy *self,
- const char *deploy_target,
- const char *revision,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- ot_lfree char *deploy_target_fullname = NULL;
- ot_lfree char *deploy_target_fullname_tmp = NULL;
- ot_lobj GFile *deploy_target_path_tmp = NULL;
- ot_lfree char *deploy_target_etc_name = NULL;
- ot_lobj GFile *deploy_target_etc_path = NULL;
- ot_lobj GFile *deploy_target_default_etc_path = NULL;
- ot_lobj GFile *deploy_parent = NULL;
- ot_lobj GFile *previous_deployment_etc = NULL;
- ot_lobj GFile *previous_deployment_etc_default = NULL;
- ot_lobj OstreeRepoFile *root = NULL;
- ot_lobj GFileInfo *file_info = NULL;
- ot_lobj GFileInfo *existing_checkout_info = NULL;
- ot_lfree char *checkout_target_name = NULL;
- ot_lfree char *checkout_target_tmp_name = NULL;
- GError *temp_error = NULL;
- gboolean skip_checkout;
-
- if (!revision)
- revision = deploy_target;
-
- if (!g_file_query_exists (self->osname_dir, cancellable))
+ if (argc < 2)
{
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No OS \"%s\" found in \"%s\"", self->osname,
- gs_file_get_path_cached (self->osname_dir));
+ ot_util_usage_error (context, "REF/REV must be specified", error);
goto out;
}
- if (!ostree_repo_resolve_rev (self->repo, revision, FALSE, &self->resolved_commit, error))
- goto out;
- if (!ostree_repo_resolve_rev (self->repo, revision, TRUE, &self->resolved_previous_commit, error))
- goto out;
-
- root = (OstreeRepoFile*)ostree_repo_file_new_root (self->repo, self->resolved_commit);
- if (!ostree_repo_file_ensure_resolved (root, error))
- goto out;
-
- file_info = g_file_query_info ((GFile*)root, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
- if (!file_info)
- goto out;
-
- deploy_target_fullname = g_strconcat (deploy_target, "-", self->resolved_commit, NULL);
- self->deploy_target_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname);
-
- deploy_target_fullname_tmp = g_strconcat (deploy_target_fullname, ".tmp", NULL);
- deploy_target_path_tmp = g_file_resolve_relative_path (self->osname_dir, deploy_target_fullname_tmp);
-
- deploy_parent = g_file_get_parent (self->deploy_target_path);
- if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
- goto out;
-
- deploy_target_etc_name = g_strconcat (deploy_target, "-", self->resolved_commit, "-etc", NULL);
- deploy_target_etc_path = g_file_resolve_relative_path (self->osname_dir, deploy_target_etc_name);
+ refspec = argv[1];
- /* Delete any previous temporary data */
- if (!gs_shutil_rm_rf (deploy_target_path_tmp, cancellable, error))
+ if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
goto out;
- existing_checkout_info = g_file_query_info (self->deploy_target_path, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, &temp_error);
- if (existing_checkout_info)
- {
- if (opt_force)
- {
- if (!gs_shutil_rm_rf (self->deploy_target_path, cancellable, error))
- goto out;
- if (!gs_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
- goto out;
-
- skip_checkout = FALSE;
- }
- else
- skip_checkout = TRUE;
- }
- else if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
- {
- g_clear_error (&temp_error);
- skip_checkout = FALSE;
- }
- else
+ if (!ot_admin_list_deployments (sysroot, ¤t_bootversion, ¤t_deployments,
+ cancellable, error))
{
- g_propagate_error (error, temp_error);
+ g_prefix_error (error, "While listing deployments: ");
goto out;
}
- if (!ot_admin_get_current_deployment (self->ostree_dir, self->osname, &self->previous_deployment,
- cancellable, error))
- goto out;
- if (self->previous_deployment)
- {
- ot_lfree char *etc_name;
- ot_lobj GFile *parent;
-
- etc_name = g_strconcat (gs_file_get_basename_cached (self->previous_deployment), "-etc", NULL);
- parent = g_file_get_parent (self->previous_deployment);
-
- previous_deployment_etc = g_file_get_child (parent, etc_name);
-
- if (!g_file_query_exists (previous_deployment_etc, cancellable)
- || g_file_equal (self->previous_deployment, self->deploy_target_path))
- g_clear_object (&previous_deployment_etc);
- else
- previous_deployment_etc_default = g_file_get_child (self->previous_deployment, "etc");
-
- if (!ostree_repo_resolve_rev (self->repo, self->current_deployment_ref, TRUE,
- &self->previous_deployment_revision, error))
- goto out;
- }
-
-
- if (!skip_checkout)
+ /* Find the currently booted deployment, if any; we will ensure it
+ * is present in the new deployment list.
+ */
+ if (!ot_admin_require_deployment_or_osname (sysroot, current_deployments,
+ opt_osname,
+ &booted_deployment,
+ cancellable, error))
{
- ProcessOneCheckoutData checkout_data;
- ot_lobj GFile *triggers_run_path = NULL;
- gs_unref_object GFile *usr_etc_path = NULL;
-
- g_print ("ostadmin: Creating deployment %s\n",
- gs_file_get_path_cached (self->deploy_target_path));
-
- memset (&checkout_data, 0, sizeof (checkout_data));
- checkout_data.loop = g_main_loop_new (NULL, TRUE);
- checkout_data.error = error;
-
- ostree_repo_checkout_tree_async (self->repo, 0, 0, deploy_target_path_tmp, root,
- file_info, cancellable,
- on_checkout_complete, &checkout_data);
-
- g_main_loop_run (checkout_data.loop);
-
- g_main_loop_unref (checkout_data.loop);
-
- if (checkout_data.caught_error)
- goto out;
-
- usr_etc_path = g_file_resolve_relative_path (deploy_target_path_tmp, "usr/etc");
- if (g_file_query_exists (usr_etc_path, NULL))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
- "Error: This tree contains usr/etc; it is likely an OS in version 2.0 format, and this version of OSTree does not support it");
- goto out;
- }
-
- triggers_run_path = g_file_resolve_relative_path (deploy_target_path_tmp, "usr/share/ostree/triggers-run");
-
- if (!g_file_query_exists (triggers_run_path, NULL))
- {
- if (!ostree_run_triggers_in_root (deploy_target_path_tmp, cancellable, error))
- goto out;
- }
-
- deploy_target_default_etc_path = ot_gfile_get_child_strconcat (deploy_target_path_tmp, "etc", NULL);
-
- if (!gs_shutil_rm_rf (deploy_target_etc_path, cancellable, error))
- goto out;
-
- if (!gs_shutil_cp_a (deploy_target_default_etc_path, deploy_target_etc_path,
- cancellable, error))
- goto out;
-
- g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deploy_target_etc_path));
-
- if (previous_deployment_etc)
- {
- if (!merge_etc_changes (self, previous_deployment_etc_default,
- previous_deployment_etc, deploy_target_etc_path,
- cancellable, error))
- goto out;
- }
- else
- g_print ("ostadmin: No previous deployment; therefore, no configuration changes to merge\n");
-
- if (!gs_file_rename (deploy_target_path_tmp, self->deploy_target_path,
- cancellable, error))
- goto out;
+ g_prefix_error (error, "Looking for booted deployment: ");
+ goto out;
}
- ret = TRUE;
- out:
- return ret;
-}
-
-/**
- * do_update_kernel:
- *
- * Ensure we have a GRUB entry, initramfs set up, etc.
- */
-static gboolean
-do_update_kernel (OtAdminDeploy *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GSSubprocess *proc = NULL;
- gs_unref_ptrarray GPtrArray *args = NULL;
-
- args = g_ptr_array_new ();
- ot_ptrarray_add_many (args, "ostree", "admin",
- "--ostree-dir", gs_file_get_path_cached (self->ostree_dir),
- "--boot-dir", gs_file_get_path_cached (self->admin_opts->boot_dir),
- "update-kernel",
- self->osname,
- gs_file_get_path_cached (self->deploy_target_path), NULL);
- g_ptr_array_add (args, NULL);
-
- proc = gs_subprocess_new_simple_argv ((char**)args->pdata,
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- cancellable, error);
- if (!proc)
- goto out;
- if (!gs_subprocess_wait_sync_check (proc, cancellable, error))
- goto out;
-
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-complete_deployment (OtAdminDeploy *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
-
- /* Write out a ref so that any "ostree prune" on the raw repo
- * doesn't GC the currently deployed tree.
- */
- if (!ostree_repo_write_ref (self->repo, NULL, self->current_deployment_ref,
- self->resolved_commit, error))
- goto out;
- /* Only overwrite previous if it's different from what we're deploying now.
- */
- if (self->resolved_previous_commit != NULL
- && strcmp (self->resolved_previous_commit, self->resolved_commit) != 0)
+ if (opt_origin_path)
{
- if (!ostree_repo_write_ref (self->repo, NULL, self->previous_deployment_ref,
- self->previous_deployment_revision, error))
+ origin = g_key_file_new ();
+
+ if (!g_key_file_load_from_file (origin, opt_origin_path, 0, error))
goto out;
}
-
- if (!update_current (self, self->previous_deployment, self->deploy_target_path,
- cancellable, error))
- goto out;
-
- ret = TRUE;
- out:
- return ret;
-}
-
-gboolean
-ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
-{
- GOptionContext *context;
- OtAdminDeploy self_data;
- OtAdminDeploy *self = &self_data;
- gboolean ret = FALSE;
- ot_lobj GFile *repo_path = NULL;
- ot_lobj GFile *deploy_path = NULL;
- const char *osname = NULL;
- const char *deploy_target = NULL;
- const char *revision = NULL;
- __attribute__((unused)) GCancellable *cancellable = NULL;
-
- memset (self, 0, sizeof (*self));
-
- context = g_option_context_new ("OSNAME TREENAME [REVISION] - In operating system OS, check out revision TREENAME (or REVISION as TREENAME)");
-
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (argc < 3)
+ else
{
- ot_util_usage_error (context, "OSNAME and TREENAME must be specified", error);
- goto out;
+ origin = ot_origin_new_from_refspec (refspec);
}
- self->admin_opts = admin_opts;
- self->ostree_dir = g_object_ref (admin_opts->ostree_dir);
-
- if (!ot_admin_ensure_initialized (self->ostree_dir, cancellable, error))
- goto out;
-
- repo_path = g_file_get_child (self->ostree_dir, "repo");
- self->repo = ostree_repo_new (repo_path);
- if (!ostree_repo_check (self->repo, error))
+ if (!ostree_repo_resolve_rev (repo, refspec, FALSE, &revision, error))
goto out;
- osname = argv[1];
- deploy_target = argv[2];
- if (argc > 3)
- revision = argv[3];
-
- self->osname = g_strdup (osname);
- self->osname_dir = ot_gfile_get_child_build_path (self->ostree_dir, "deploy", osname, NULL);
- self->current_deployment_ref = g_strdup_printf ("deployment/%s/current", self->osname);
- self->previous_deployment_ref = g_strdup_printf ("deployment/%s/previous", self->osname);
-
- if (!deploy_tree (self, deploy_target, revision, cancellable, error))
- goto out;
-
- if (!opt_no_kernel)
- {
- if (!do_update_kernel (self, cancellable, error))
- goto out;
- }
-
- if (!complete_deployment (self, cancellable, error))
+ if (!ot_admin_deploy (sysroot, current_bootversion, current_deployments,
+ opt_osname, revision, origin,
+ opt_kernel_argv, opt_retain,
+ booted_deployment, NULL,
+ &new_deployment, &new_bootversion, &new_deployments,
+ cancellable, error))
goto out;
ret = TRUE;
out:
- g_clear_object (&self->repo);
- g_free (self->osname);
- g_free (self->current_deployment_ref);
- g_free (self->previous_deployment_ref);
- g_free (self->resolved_commit);
- g_free (self->resolved_previous_commit);
- g_free (self->previous_deployment_revision);
- g_clear_object (&self->previous_deployment);
- g_clear_object (&self->ostree_dir);
- g_clear_object (&self->osname_dir);
+ if (origin)
+ g_key_file_unref (origin);
if (context)
g_option_context_free (context);
return ret;
#include <glib/gi18n.h>
+static char *opt_osname;
+
static GOptionEntry options[] = {
+ { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
{ NULL }
};
{
GOptionContext *context;
gboolean ret = FALSE;
- const char *osname;
- GFile *ostree_dir = admin_opts->ostree_dir;
+ gs_free char *booted_osname = NULL;
ot_lobj GFile *repo_path = NULL;
- ot_lobj GFile *deployment = NULL;
+ gs_unref_object OtDeployment *deployment = NULL;
+ gs_unref_object GFile *deployment_dir = NULL;
ot_lobj GFile *deploy_parent = NULL;
ot_lptrarray GPtrArray *modified = NULL;
ot_lptrarray GPtrArray *removed = NULL;
ot_lptrarray GPtrArray *added = NULL;
+ gs_unref_ptrarray GPtrArray *deployments = NULL;
ot_lobj GFile *orig_etc_path = NULL;
ot_lobj GFile *new_etc_path = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
+ int bootversion;
- context = g_option_context_new ("OSNAME [REVISION] - Diff configuration for OSNAME");
+ context = g_option_context_new ("Diff current /etc configuration versus default");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
- repo_path = g_file_get_child (ostree_dir, "repo");
+ repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
- if (argc < 2)
+ if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
+ cancellable, error))
{
- ot_util_usage_error (context, "OSNAME must be specified", error);
+ g_prefix_error (error, "While listing deployments: ");
goto out;
}
- osname = argv[1];
-
- if (argc > 2)
- {
- deployment = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, argv[2], NULL);
- if (!g_file_query_exists (deployment, NULL))
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Deployment %s doesn't exist", gs_file_get_path_cached (deployment));
- goto out;
- }
- }
- else
+ if (!ot_admin_require_deployment_or_osname (admin_opts->sysroot, deployments,
+ opt_osname, &deployment,
+ cancellable, error))
+ goto out;
+ if (deployment != NULL)
+ opt_osname = (char*)ot_deployment_get_osname (deployment);
+ if (deployment == NULL)
+ deployment = ot_admin_get_merge_deployment (deployments, opt_osname, deployment, NULL);
+ if (deployment == NULL)
{
- if (!ot_admin_get_current_deployment (ostree_dir, osname, &deployment,
- cancellable, error))
- goto out;
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "No deployment for OS '%s'", opt_osname);
+ goto out;
}
- orig_etc_path = g_file_resolve_relative_path (deployment, "etc");
- deploy_parent = g_file_get_parent (deployment);
- new_etc_path = ot_gfile_get_child_strconcat (deploy_parent,
- gs_file_get_basename_cached (deployment),
- "-etc", NULL);
+ deployment_dir = ot_admin_get_deployment_directory (admin_opts->sysroot, deployment);
+
+ orig_etc_path = g_file_resolve_relative_path (deployment_dir, "usr/etc");
+ new_etc_path = g_file_resolve_relative_path (deployment_dir, "etc");
modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
goto out;
g_clear_object (&child);
- child = g_file_get_child (dir, "ostree");
- if (!ot_admin_ensure_initialized (child, cancellable, error))
+ if (!ot_admin_ensure_initialized (dir, cancellable, error))
goto out;
ret = TRUE;
gboolean ret = FALSE;
const char *keyfile_arg = NULL;
const char *treename_arg = NULL;
- GFile *ostree_dir = admin_opts->ostree_dir;
ot_lobj GFile *deploy_dir = NULL;
ot_lobj GFile *osdir = NULL;
ot_lobj GFile *dest_osconfig_path = NULL;
goto out;
}
- if (admin_opts->ostree_dir == NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No existing /ostree found; use --ostree-dir");
- goto out;
- }
-
- if (!ot_admin_ensure_initialized (admin_opts->ostree_dir, cancellable, error))
+ if (!ot_admin_ensure_initialized (admin_opts->sysroot, cancellable, error))
goto out;
self->loop = g_main_loop_new (NULL, TRUE);
osname = g_key_file_get_string (keyfile, "os", "Name", error);
- ostree_dir_arg = g_strconcat ("--ostree-dir=",
- gs_file_get_path_cached (ostree_dir),
+ ostree_dir_arg = g_strconcat ("--sysroot=",
+ gs_file_get_path_cached (admin_opts->sysroot),
NULL);
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+ if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", "admin", ostree_dir_arg, "os-init", osname, NULL))
goto out;
}
- osdir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+ osdir = ot_gfile_get_child_build_path (admin_opts->sysroot, "ostree", "deploy", osname, NULL);
dest_osconfig_path = ot_gfile_get_child_strconcat (osdir, osname, ".cfg", NULL);
if (!g_file_copy (self->osconfig_path, dest_osconfig_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_TARGET_DEFAULT_PERMS,
goto out;
repoarg = g_strconcat ("--repo=",
- gs_file_get_path_cached (ostree_dir), "/repo",
+ gs_file_get_path_cached (admin_opts->sysroot), "/ostree/repo",
NULL);
{
if (!repourl)
goto out;
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+ if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", repoarg, "remote", "add",
goto out;
}
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+ if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", "pull", repoarg, osname, NULL))
goto out;
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+ if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", "admin", ostree_dir_arg, "deploy", osname,
GOptionContext *context;
gboolean ret = FALSE;
const char *osname = NULL;
- GFile *ostree_dir = admin_opts->ostree_dir;
ot_lobj GFile *deploy_dir = NULL;
ot_lobj GFile *dir = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
- if (!ot_admin_ensure_initialized (ostree_dir, cancellable, error))
+ if (!ot_admin_ensure_initialized (admin_opts->sysroot, cancellable, error))
goto out;
if (argc < 2)
osname = argv[1];
- deploy_dir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
+ deploy_dir = ot_gfile_get_child_build_path (admin_opts->sysroot, "ostree", "deploy", osname, NULL);
/* Ensure core subdirectories of /var exist, since we need them for
* dracut generation, and the host will want them too. Note that at
{
GOptionContext *context;
gboolean ret = FALSE;
- guint i;
const char *osname;
- GFile *ostree_dir = admin_opts->ostree_dir;
ot_lobj GFile *repo_path = NULL;
ot_lobj GFile *deploy_dir = NULL;
ot_lobj GFile *current_deployment = NULL;
ot_lobj GFile *previous_deployment = NULL;
ot_lobj GFile *active_deployment = NULL;
- ot_lptrarray GPtrArray *deployments = NULL;
gs_free char *active_osname = NULL;
__attribute__((unused)) GCancellable *cancellable = NULL;
osname = argv[1];
- if (!ot_admin_list_deployments (ostree_dir, osname, &deployments,
- cancellable, error))
+ if (!ot_admin_cleanup (admin_opts->sysroot, cancellable, error))
goto out;
- if (!ot_admin_get_current_deployment (ostree_dir, osname, ¤t_deployment,
- cancellable, error));
- if (!ot_admin_get_previous_deployment (ostree_dir, osname, &previous_deployment,
- cancellable, error));
- if (!ot_admin_get_active_deployment (ostree_dir, &active_osname, &active_deployment,
- cancellable, error));
-
- for (i = 0; i < deployments->len; i++)
- {
- GFile *deployment = deployments->pdata[i];
- ot_lobj GFile *deployment_etc = NULL;
- ot_lobj GFile *parent = NULL;
-
- if ((current_deployment && g_file_equal (deployment, current_deployment))
- || (previous_deployment && g_file_equal (deployment, previous_deployment))
- || (active_deployment && g_file_equal (deployment, active_deployment)))
- continue;
-
- parent = g_file_get_parent (deployment);
- deployment_etc = ot_gfile_get_child_strconcat (parent, gs_file_get_basename_cached (deployment),
- "-etc", NULL);
-
- g_print ("Deleting deployment %s\n", gs_file_get_path_cached (deployment));
- if (!gs_shutil_rm_rf (deployment, cancellable, error))
- goto out;
- /* Note - not atomic; we may be leaving the -etc directory around
- * if this fails in the middle =/
- */
- if (!gs_shutil_rm_rf (deployment_etc, cancellable, error))
- goto out;
- }
-
- repo_path = g_file_get_child (ostree_dir, "repo");
+ repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
if (!opt_no_repo_prune)
{
repo_arg = g_strconcat ("--repo=", gs_file_get_path_cached (repo_path), NULL);
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
+ if (!gs_subprocess_simple_run_sync (NULL,
GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
"ostree", repo_arg, "prune", "--refs-only",
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2012 Colin Walters <walters@verbum.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ot-admin-builtins.h"
-#include "ot-admin-functions.h"
-#include "ostree.h"
-
-#include <glib/gi18n.h>
-
-static gboolean opt_no_kernel;
-
-static GOptionEntry options[] = {
- { "no-kernel", 0, 0, G_OPTION_ARG_NONE, &opt_no_kernel, "Don't update kernel related config (initramfs, bootloader)", NULL },
- { NULL }
-};
-
-static gboolean
-ensure_remote_branch (OstreeRepo *repo,
- const char *remote,
- const char *branch,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gchar **iter = NULL;
- gsize len;
- gs_free char *remote_key = NULL;
- gs_unref_ptrarray GPtrArray *new_branches = NULL;
- GKeyFile *config = NULL;
- gchar **branches = NULL;
- gboolean have_branch = FALSE;
-
- config = ostree_repo_copy_config (repo);
- remote_key = g_strdup_printf ("remote \"%s\"", remote);
-
- new_branches = g_ptr_array_new ();
-
- branches = g_key_file_get_string_list (config, remote_key, "branches", &len, error);
- if (!branches)
- goto out;
-
- for (iter = branches; *iter; iter++)
- {
- char *item = *iter;
- if (!have_branch)
- have_branch = strcmp (item, branch) == 0;
- g_ptr_array_add (new_branches, item);
- }
-
- if (!have_branch)
- {
- g_ptr_array_add (new_branches, (char*)branch);
- g_key_file_set_string_list (config, remote_key, "branches",
- (const char *const *)new_branches->pdata,
- new_branches->len);
-
- if (!ostree_repo_write_config (repo, config, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- if (config)
- g_key_file_free (config);
- if (branches)
- g_strfreev (branches);
- return ret;
-}
-
-gboolean
-ot_admin_builtin_pull_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
-{
- GOptionContext *context;
- gboolean ret = FALSE;
- const char *osname;
- const char *target;
- GFile *ostree_dir = admin_opts->ostree_dir;
- ot_lobj OstreeRepo *repo = NULL;
- ot_lobj GFile *repo_path = NULL;
- ot_lobj GFile *current_deployment = NULL;
- ot_lfree char *deploy_name = NULL;
- ot_lobj GFile *deploy_dir = NULL;
- ot_lfree char *remote_name = NULL;
- ot_lptrarray GPtrArray *subproc_args = NULL;
- __attribute__((unused)) GCancellable *cancellable = NULL;
-
- context = g_option_context_new ("OSNAME [TREE] - Ensure TREE (default current) is in list of remotes, then download and deploy");
-
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (argc < 2)
- {
- ot_util_usage_error (context, "OSNAME must be specified", error);
- goto out;
- }
-
- osname = argv[1];
-
- repo_path = g_file_get_child (ostree_dir, "repo");
-
- repo = ostree_repo_new (repo_path);
- if (!ostree_repo_check (repo, error))
- goto out;
-
- if (argc > 2)
- {
- target = argv[2];
- if (!ensure_remote_branch (repo, osname, target,
- cancellable, error))
- goto out;
-
- deploy_name = g_strdup (target);
- }
- else
- {
- if (!ot_admin_get_current_deployment (ostree_dir, osname, ¤t_deployment,
- cancellable, error))
- goto out;
-
- if (!current_deployment)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No current deployment");
- goto out;
- }
-
- ot_admin_parse_deploy_name (ostree_dir, osname, current_deployment,
- &deploy_name, NULL);
- }
-
- if (!ot_admin_pull (ostree_dir, osname, cancellable, error))
- goto out;
-
- {
- ot_lfree char *opt_ostree_dir_arg = g_strconcat ("--ostree-dir=",
- gs_file_get_path_cached (ostree_dir),
- NULL);
- ot_lfree char *opt_boot_dir_arg = g_strconcat ("--boot-dir=",
- gs_file_get_path_cached (admin_opts->boot_dir),
- NULL);
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- cancellable, error,
- "ostree", "admin", opt_ostree_dir_arg, opt_boot_dir_arg, "deploy", osname,
- deploy_name, NULL))
- goto out;
- }
-
- ret = TRUE;
- out:
- if (context)
- g_option_context_free (context);
- return ret;
-}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012,2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree.h"
+
+#include <glib/gi18n.h>
+
+static GOptionEntry options[] = {
+ { NULL }
+};
+
+gboolean
+ot_admin_builtin_status (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
+{
+ GOptionContext *context;
+ gboolean ret = FALSE;
+ int bootversion;
+ gs_unref_object OtDeployment *booted_deployment = NULL;
+ gs_unref_ptrarray GPtrArray *deployments = NULL;
+ __attribute__((unused)) GCancellable *cancellable = NULL;
+ guint i;
+
+ context = g_option_context_new ("List deployments");
+
+ g_option_context_add_main_entries (context, options, NULL);
+
+ if (!g_option_context_parse (context, &argc, &argv, error))
+ goto out;
+
+ if (!ot_admin_list_deployments (admin_opts->sysroot, &bootversion, &deployments,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While listing deployments: ");
+ goto out;
+ }
+
+ /* Find the currently booted deployment, if any; we will
+ * ensure it is present in the new deployment list.
+ */
+ if (!ot_admin_find_booted_deployment (admin_opts->sysroot, deployments,
+ &booted_deployment,
+ cancellable, error))
+ goto out;
+
+ if (deployments->len == 0)
+ {
+ g_print ("No deployments.\n");
+ }
+ else
+ {
+ int subbootversion;
+
+ if (!ot_admin_read_current_subbootversion (admin_opts->sysroot, bootversion,
+ &subbootversion,
+ cancellable, error))
+ goto out;
+
+ g_print ("bootversion: %d.%d\n", bootversion, subbootversion);
+
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+ g_print ("%u: %c %s %s.%d\n",
+ i,
+ deployment == booted_deployment ? '*' : ' ',
+ ot_deployment_get_osname (deployment),
+ ot_deployment_get_csum (deployment),
+ ot_deployment_get_deployserial (deployment));
+ }
+ }
+
+ ret = TRUE;
+ out:
+ if (context)
+ g_option_context_free (context);
+ return ret;
+}
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2012 Colin Walters <walters@verbum.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include "config.h"
-
-#include "ot-admin-builtins.h"
-#include "ostree.h"
-
-#include <glib/gi18n.h>
-#include <sys/utsname.h>
-
-typedef struct {
- OtAdminBuiltinOpts *admin_opts;
- GFile *ostree_dir;
- GFile *boot_ostree_dir;
- GFile *deploy_path;
- GFile *kernel_path;
- char *release;
- char *osname;
-} OtAdminUpdateKernel;
-
-static gboolean opt_no_bootloader;
-
-static GOptionEntry options[] = {
- { "no-bootloader", 0, 0, G_OPTION_ARG_NONE, &opt_no_bootloader, "Don't update bootloader", NULL },
- { NULL }
-};
-
-static gboolean
-get_kernel_from_boot (GFile *path,
- GFile **out_kernel,
- GFile **out_initramfs,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GFileEnumerator *dir_enum = NULL;
- gs_unref_object GFileInfo *file_info = NULL;
- gs_unref_object GFile *ret_kernel = NULL;
- gs_unref_object GFile *ret_initramfs = NULL;
-
- dir_enum = g_file_enumerate_children (path, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL, error);
- if (!dir_enum)
- goto out;
-
- while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, error)) != NULL)
- {
- const char *name;
-
- name = g_file_info_get_name (file_info);
-
- if (ret_kernel == NULL && g_str_has_prefix (name, "vmlinuz-"))
- ret_kernel = g_file_get_child (path, name);
- else if (ret_initramfs == NULL && g_str_has_prefix (name, "initramfs-"))
- ret_initramfs = g_file_get_child (path, name);
-
- if (ret_kernel && ret_initramfs)
- break;
- }
-
- ot_transfer_out_value (out_kernel, &ret_kernel);
- ot_transfer_out_value (out_initramfs, &ret_initramfs);
- ret = TRUE;
- out:
- return ret;
-}
-
-static gboolean
-grep_literal (GFile *f,
- const char *string,
- gboolean *out_matches,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gboolean ret_matches = FALSE;
- gs_unref_object GInputStream *in = NULL;
- gs_unref_object GDataInputStream *datain = NULL;
- ot_lfree char *line = NULL;
-
- in = (GInputStream*)g_file_read (f, cancellable, error);
- if (!in)
- goto out;
- datain = (GDataInputStream*)g_data_input_stream_new (in);
- if (!in)
- goto out;
-
- while ((line = g_data_input_stream_read_line (datain, NULL, cancellable, error)) != NULL)
- {
- if (strstr (line, string))
- {
- ret_matches = TRUE;
- break;
- }
-
- g_free (line);
- }
-
- ret = TRUE;
- if (out_matches)
- *out_matches = ret_matches;
- out:
- return ret;
-}
-
-static gboolean
-update_grub (OtAdminUpdateKernel *self,
- GCancellable *cancellable,
- GError **error)
-{
- gboolean ret = FALSE;
- gs_unref_object GFile *grub_path = g_file_resolve_relative_path (self->admin_opts->boot_dir, "grub/grub.conf");
-
- if (g_file_query_exists (grub_path, cancellable))
- {
- gboolean have_grub_entry;
- if (!grep_literal (grub_path, "OSTree", &have_grub_entry,
- cancellable, error))
- goto out;
-
- if (!have_grub_entry)
- {
- ot_lfree char *add_kernel_arg = NULL;
- ot_lfree char *initramfs_arg = NULL;
- ot_lfree char *initramfs_name = NULL;
- gs_unref_object GFile *initramfs_path = NULL;
-
- initramfs_name = g_strconcat ("initramfs-", self->release, ".img", NULL);
- initramfs_path = g_file_get_child (self->boot_ostree_dir, initramfs_name);
-
- add_kernel_arg = g_strconcat ("--add-kernel=", gs_file_get_path_cached (self->kernel_path), NULL);
- initramfs_arg = g_strconcat ("--initrd=", gs_file_get_path_cached (initramfs_path), NULL);
-
- g_print ("Adding OSTree grub entry...\n");
- if (!gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_NULL,
- cancellable, error,
- "grubby", "--grub", add_kernel_arg, initramfs_arg,
- "--copy-default", "--title=OSTree", NULL))
- goto out;
- }
- else
- g_print ("Already have OSTree entry in grub config\n");
- }
- else
- {
- g_print ("/boot/grub/grub.conf not found, assuming you have GRUB 2\n");
- }
-
- ret = TRUE;
- out:
- return ret;
-}
-
-gboolean
-ot_admin_builtin_update_kernel (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
-{
- gboolean ret = FALSE;
- GOptionContext *context;
- OtAdminUpdateKernel self_data;
- OtAdminUpdateKernel *self = &self_data;
- GFile *ostree_dir = admin_opts->ostree_dir;
- gs_unref_object GFile *deploy_boot_path = NULL;
- gs_unref_object GFile *src_kernel_path = NULL;
- gs_unref_object GFile *src_initramfs_path = NULL;
- gs_free char *prefix = NULL;
- gs_free char *initramfs_name = NULL;
- gs_unref_object GFile *expected_initramfs_path = NULL;
- const char *release = NULL;
- const char *kernel_name = NULL;
- GCancellable *cancellable = NULL;
-
- memset (self, 0, sizeof (*self));
-
- self->admin_opts = admin_opts;
-
- context = g_option_context_new ("OSNAME [DEPLOY_PATH] - Update kernel and regenerate initial ramfs");
- g_option_context_add_main_entries (context, options, NULL);
-
- if (!g_option_context_parse (context, &argc, &argv, error))
- goto out;
-
- if (argc < 2)
- {
- ot_util_usage_error (context, "OSNAME must be specified", error);
- goto out;
- }
-
- self->osname = g_strdup (argv[1]);
-
- if (argc > 2)
- self->deploy_path = g_file_new_for_path (argv[2]);
- else
- {
- gs_unref_object GFile *osdir = ot_gfile_get_child_build_path (admin_opts->ostree_dir, "deploy", self->osname, NULL);
- self->deploy_path = g_file_get_child (osdir, "current");
- }
-
- self->ostree_dir = g_object_ref (ostree_dir);
- self->boot_ostree_dir = g_file_get_child (admin_opts->boot_dir, "ostree");
-
- deploy_boot_path = g_file_get_child (self->deploy_path, "boot");
-
- if (!get_kernel_from_boot (deploy_boot_path, &src_kernel_path, &src_initramfs_path,
- cancellable, error))
- goto out;
-
- if (src_kernel_path == NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "No kernel found in %s", gs_file_get_path_cached (deploy_boot_path));
- goto out;
- }
-
- if (!gs_file_ensure_directory (self->boot_ostree_dir, TRUE, cancellable, error))
- goto out;
-
- kernel_name = gs_file_get_basename_cached (src_kernel_path);
- release = strchr (kernel_name, '-');
- if (release == NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Invalid kernel name %s, no - found", gs_file_get_path_cached (src_kernel_path));
- goto out;
- }
-
- self->release = g_strdup (release + 1);
- prefix = g_strndup (kernel_name, release - kernel_name);
- self->kernel_path = ot_gfile_get_child_strconcat (self->boot_ostree_dir, prefix, "-", self->release, NULL);
-
- if (!g_file_query_exists(self->kernel_path, NULL))
- {
- if (!gs_file_linkcopy_sync_data (src_kernel_path, self->kernel_path, G_FILE_COPY_OVERWRITE,
- cancellable, error))
- goto out;
- g_print ("ostadmin: Deployed kernel %s\n", gs_file_get_path_cached (self->kernel_path));
- }
-
- initramfs_name = g_strconcat ("initramfs-", self->release, ".img", NULL);
- expected_initramfs_path = g_file_get_child (self->boot_ostree_dir, initramfs_name);
-
- if (!g_file_query_exists (expected_initramfs_path, NULL))
- {
- if (!gs_file_linkcopy_sync_data (src_initramfs_path, expected_initramfs_path, G_FILE_COPY_OVERWRITE,
- cancellable, error))
- goto out;
-
- /* In the fuse case, we need to chown after copying */
- if (getuid () != 0)
- {
- if (!gs_file_chown (expected_initramfs_path, 0, 0, cancellable, error))
- {
- g_prefix_error (error, "Failed to chown initramfs: ");
- goto out;
- }
- }
-
- g_print ("Deployed initramfs: %s\n", gs_file_get_path_cached (expected_initramfs_path));
- }
-
- if (!opt_no_bootloader)
- {
- if (!update_grub (self, cancellable, error))
- goto out;
- }
-
- ret = TRUE;
- out:
- g_clear_object (&self->ostree_dir);
- g_clear_object (&self->boot_ostree_dir);
- g_clear_object (&self->kernel_path);
- g_free (self->release);
- if (context)
- g_option_context_free (context);
- return ret;
-}
#include "ot-admin-builtins.h"
#include "ot-admin-functions.h"
+#include "ot-admin-deploy.h"
#include "ostree.h"
#include "otutil.h"
#include <glib/gi18n.h>
static gboolean opt_reboot;
+static char *opt_osname;
static GOptionEntry options[] = {
+ { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL },
{ "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL },
{ NULL }
};
gboolean
ot_admin_builtin_upgrade (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error)
{
- GOptionContext *context;
gboolean ret = FALSE;
- GFile *ostree_dir = admin_opts->ostree_dir;
+ __attribute__((unused)) GCancellable *cancellable = NULL;
+ GOptionContext *context;
+ GFile *sysroot = admin_opts->sysroot;
gs_free char *booted_osname = NULL;
- const char *osname = NULL;
- gs_unref_object GFile *deployment = NULL;
- gs_unref_object GFile *repo_path = NULL;
gs_unref_object OstreeRepo *repo = NULL;
- gs_free char *deploy_name = NULL;
- gs_free char *current_rev = NULL;
- gs_free char *new_rev = NULL;
+ gs_unref_object GFile *repo_path = NULL;
+ gs_free char *origin_refspec = NULL;
+ gs_free char *origin_remote = NULL;
+ gs_free char *origin_ref = NULL;
+ gs_free char *new_revision = NULL;
+ gs_unref_object GFile *deployment_path = NULL;
+ gs_unref_object GFile *deployment_origin_path = NULL;
+ gs_unref_object OtDeployment *booted_deployment = NULL;
+ gs_unref_object OtDeployment *merge_deployment = NULL;
+ gs_unref_ptrarray GPtrArray *current_deployments = NULL;
+ gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+ gs_unref_object OtDeployment *new_deployment = NULL;
gs_free char *ostree_dir_arg = NULL;
- __attribute__((unused)) GCancellable *cancellable = NULL;
+ int current_bootversion;
+ int new_bootversion;
+ GKeyFile *origin;
- context = g_option_context_new ("[OSNAME] - pull, deploy, and prune");
+ context = g_option_context_new ("Construct new tree from current origin and deploy it, if it changed");
g_option_context_add_main_entries (context, options, NULL);
if (!g_option_context_parse (context, &argc, &argv, error))
goto out;
- if (argc > 1)
- {
- osname = argv[1];
- }
- else
+ if (!ot_admin_list_deployments (admin_opts->sysroot, ¤t_bootversion,
+ ¤t_deployments,
+ cancellable, error))
{
- if (!ot_admin_get_booted_os (&booted_osname, NULL,
- cancellable, error))
- goto out;
- if (booted_osname == NULL)
- {
- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Not in an active OSTree system; must specify OSNAME");
- goto out;
- }
- osname = booted_osname;
+ g_prefix_error (error, "While listing deployments: ");
+ goto out;
}
-
- if (!ot_admin_get_current_deployment (ostree_dir, osname, &deployment,
- cancellable, error))
- goto out;
-
- ot_admin_parse_deploy_name (ostree_dir, osname, deployment, &deploy_name, ¤t_rev);
- ostree_dir_arg = g_strconcat ("--ostree-dir=",
- gs_file_get_path_cached (ostree_dir),
- NULL);
-
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- cancellable, error,
- "ostree", "admin", ostree_dir_arg, "pull-deploy", osname, NULL))
+ if (!ot_admin_require_deployment_or_osname (admin_opts->sysroot, current_deployments,
+ opt_osname,
+ &booted_deployment,
+ cancellable, error))
goto out;
-
- if (!gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- cancellable, error,
- "ostree", "admin", ostree_dir_arg, "prune", osname, NULL))
+ if (!opt_osname)
+ opt_osname = (char*)ot_deployment_get_osname (booted_deployment);
+ merge_deployment = ot_admin_get_merge_deployment (current_deployments, opt_osname,
+ booted_deployment,
+ NULL);
+
+ deployment_path = ot_admin_get_deployment_directory (admin_opts->sysroot, merge_deployment);
+ deployment_origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+
+ repo_path = g_file_resolve_relative_path (admin_opts->sysroot, "ostree/repo");
+ repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (repo, error))
goto out;
- if (opt_reboot)
+ origin = ot_deployment_get_origin (merge_deployment);
+ if (!origin)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No origin known for current deployment");
+ goto out;
+ }
+ origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL);
+ if (!origin_refspec)
{
- repo_path = g_file_get_child (ostree_dir, "repo");
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No origin/refspec in current deployment origin; cannot upgrade via ostree");
+ goto out;
+ }
+ if (!ostree_parse_refspec (origin_refspec, &origin_remote, &origin_ref, error))
+ goto out;
- repo = ostree_repo_new (repo_path);
- if (!ostree_repo_check (repo, error))
+ if (origin_remote)
+ {
+ g_print ("Fetching remote %s ref %s\n", origin_remote, origin_ref);
+ if (!ot_admin_pull (admin_opts->sysroot, origin_remote, origin_ref,
+ cancellable, error))
goto out;
+ }
- if (!ostree_repo_resolve_rev (repo, deploy_name, TRUE, &new_rev,
- error))
+ if (!ostree_repo_resolve_rev (repo, origin_ref, FALSE, &new_revision,
+ error))
+ goto out;
+
+ if (strcmp (ot_deployment_get_csum (merge_deployment), new_revision) == 0)
+ {
+ g_print ("Refspec %s is unchanged\n", origin_refspec);
+ }
+ else
+ {
+ gs_unref_object GFile *real_sysroot = g_file_new_for_path ("/");
+ if (!ot_admin_deploy (admin_opts->sysroot,
+ current_bootversion, current_deployments,
+ opt_osname, new_revision, origin,
+ NULL, FALSE,
+ booted_deployment, merge_deployment,
+ &new_deployment, &new_bootversion, &new_deployments,
+ cancellable, error))
goto out;
- if (strcmp (current_rev, new_rev) != 0 && opt_reboot)
+ if (opt_reboot && g_file_equal (sysroot, real_sysroot))
{
gs_subprocess_simple_run_sync (NULL, GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
cancellable, error,
G_BEGIN_DECLS
typedef struct {
- GFile *ostree_dir;
- GFile *boot_dir;
+ GFile *sysroot;
} OtAdminBuiltinOpts;
gboolean ot_admin_builtin_os_init (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_init_fs (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_prune (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
-gboolean ot_admin_builtin_pull_deploy (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
+gboolean ot_admin_builtin_status (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_diff (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_run_triggers (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
-gboolean ot_admin_builtin_update_kernel (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
gboolean ot_admin_builtin_upgrade (int argc, char **argv, OtAdminBuiltinOpts *admin_opts, GError **error);
G_END_DECLS
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#include "config.h"
+
+#include "ot-admin-functions.h"
+#include "ot-admin-deploy.h"
+#include "ot-deployment.h"
+#include "ot-config-parser.h"
+#include "ot-bootloader-syslinux.h"
+#include "otutil.h"
+#include "ostree-core.h"
+#include "libgsystem.h"
+
+typedef struct {
+ GError **error;
+ gboolean caught_error;
+
+ GMainLoop *loop;
+} ProcessOneCheckoutData;
+
+static void
+on_checkout_complete (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ProcessOneCheckoutData *data = user_data;
+ GError *local_error = NULL;
+
+ if (!ostree_repo_checkout_tree_finish ((OstreeRepo*)object, result,
+ &local_error))
+ goto out;
+
+ out:
+ if (local_error)
+ {
+ data->caught_error = TRUE;
+ g_propagate_error (data->error, local_error);
+ }
+ g_main_loop_quit (data->loop);
+}
+
+
+/**
+ * copy_one_config_file:
+ *
+ * Copy @file from @modified_etc to @new_etc, overwriting any existing
+ * file there.
+ */
+static gboolean
+copy_one_config_file (GFile *orig_etc,
+ GFile *modified_etc,
+ GFile *new_etc,
+ GFile *src,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ ot_lobj GFileInfo *src_info = NULL;
+ ot_lobj GFile *dest = NULL;
+ ot_lobj GFile *parent = NULL;
+ ot_lfree char *relative_path = NULL;
+
+ relative_path = g_file_get_relative_path (modified_etc, src);
+ g_assert (relative_path);
+ dest = g_file_resolve_relative_path (new_etc, relative_path);
+
+ src_info = g_file_query_info (src, OSTREE_GIO_FAST_QUERYINFO, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!src_info)
+ goto out;
+
+ if (g_file_info_get_file_type (src_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ ot_lobj GFileEnumerator *src_enum = NULL;
+ ot_lobj GFileInfo *child_info = NULL;
+ GError *temp_error = NULL;
+
+ /* FIXME actually we need to copy permissions and xattrs */
+ if (!gs_file_ensure_directory (dest, TRUE, cancellable, error))
+ goto out;
+
+ src_enum = g_file_enumerate_children (src, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+
+ while ((child_info = g_file_enumerator_next_file (src_enum, cancellable, error)) != NULL)
+ {
+ ot_lobj GFile *child = g_file_get_child (src, g_file_info_get_name (child_info));
+
+ if (!copy_one_config_file (orig_etc, modified_etc, new_etc, child,
+ cancellable, error))
+ goto out;
+ }
+ g_clear_object (&child_info);
+ if (temp_error != NULL)
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
+ else
+ {
+ parent = g_file_get_parent (dest);
+
+ /* FIXME actually we need to copy permissions and xattrs */
+ if (!gs_file_ensure_directory (parent, TRUE, cancellable, error))
+ goto out;
+
+ /* We unlink here because otherwise gio throws an error on
+ * dangling symlinks.
+ */
+ if (!ot_gfile_ensure_unlinked (dest, cancellable, error))
+ goto out;
+
+ if (!g_file_copy (src, dest, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+ cancellable, NULL, NULL, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * merge_etc_changes:
+ *
+ * Compute the difference between @orig_etc and @modified_etc,
+ * and apply that to @new_etc.
+ *
+ * The algorithm for computing the difference is pretty simple; it's
+ * approximately equivalent to "diff -unR orig_etc modified_etc",
+ * except that rather than attempting a 3-way merge if a file is also
+ * changed in @new_etc, the modified version always wins.
+ */
+static gboolean
+merge_etc_changes (GFile *orig_etc,
+ GFile *modified_etc,
+ GFile *new_etc,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ ot_lobj GFile *ostree_etc = NULL;
+ ot_lobj GFile *tmp_etc = NULL;
+ ot_lptrarray GPtrArray *modified = NULL;
+ ot_lptrarray GPtrArray *removed = NULL;
+ ot_lptrarray GPtrArray *added = NULL;
+ guint i;
+
+ modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
+ removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+
+ if (!ostree_diff_dirs (orig_etc, modified_etc, modified, removed, added,
+ cancellable, error))
+ {
+ g_prefix_error (error, "While computing configuration diff: ");
+ goto out;
+ }
+
+ if (modified->len > 0 || removed->len > 0 || added->len > 0)
+ g_print ("ostadmin: Processing config: %u modified, %u removed, %u added\n",
+ modified->len,
+ removed->len,
+ added->len);
+ else
+ g_print ("ostadmin: No modified configuration\n");
+
+ for (i = 0; i < removed->len; i++)
+ {
+ GFile *file = removed->pdata[i];
+ ot_lobj GFile *target_file = NULL;
+ ot_lfree char *path = NULL;
+
+ path = g_file_get_relative_path (orig_etc, file);
+ g_assert (path);
+ target_file = g_file_resolve_relative_path (new_etc, path);
+
+ if (!ot_gfile_ensure_unlinked (target_file, cancellable, error))
+ goto out;
+ }
+
+ for (i = 0; i < modified->len; i++)
+ {
+ OstreeDiffItem *diff = modified->pdata[i];
+
+ if (!copy_one_config_file (orig_etc, modified_etc, new_etc, diff->target,
+ cancellable, error))
+ goto out;
+ }
+ for (i = 0; i < added->len; i++)
+ {
+ GFile *file = added->pdata[i];
+
+ if (!copy_one_config_file (orig_etc, modified_etc, new_etc, file,
+ cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/**
+ * checkout_deployment_tree:
+ *
+ * Look up @revision in the repository, and check it out in
+ * /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
+ */
+static gboolean
+checkout_deployment_tree (GFile *sysroot,
+ OstreeRepo *repo,
+ OtDeployment *deployment,
+ GFile **out_deployment_path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const char *csum = ot_deployment_get_csum (deployment);
+ gs_unref_object OstreeRepoFile *root = NULL;
+ gs_unref_object GFileInfo *file_info = NULL;
+ gs_unref_object GFileInfo *existing_checkout_info = NULL;
+ gs_free char *checkout_target_name = NULL;
+ gs_free char *checkout_target_tmp_name = NULL;
+ gs_unref_object GFile *osdeploy_path = NULL;
+ gs_unref_object GFile *deploy_target_path = NULL;
+ gs_unref_object GFile *deploy_parent = NULL;
+ ProcessOneCheckoutData checkout_data;
+
+ root = (OstreeRepoFile*)ostree_repo_file_new_root (repo, csum);
+ if (!ostree_repo_file_ensure_resolved (root, error))
+ goto out;
+
+ file_info = g_file_query_info ((GFile*)root, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!file_info)
+ goto out;
+
+ osdeploy_path = ot_gfile_get_child_build_path (sysroot, "ostree", "deploy",
+ ot_deployment_get_osname (deployment),
+ "deploy", NULL);
+ checkout_target_name = g_strdup_printf ("%s.%d", csum, ot_deployment_get_deployserial (deployment));
+ deploy_target_path = g_file_get_child (osdeploy_path, checkout_target_name);
+
+ deploy_parent = g_file_get_parent (deploy_target_path);
+ if (!gs_file_ensure_directory (deploy_parent, TRUE, cancellable, error))
+ goto out;
+
+ g_print ("ostadmin: Creating deployment %s\n",
+ gs_file_get_path_cached (deploy_target_path));
+
+ memset (&checkout_data, 0, sizeof (checkout_data));
+ checkout_data.loop = g_main_loop_new (NULL, TRUE);
+ checkout_data.error = error;
+
+ ostree_repo_checkout_tree_async (repo, 0, 0, deploy_target_path, root,
+ file_info, cancellable,
+ on_checkout_complete, &checkout_data);
+
+ g_main_loop_run (checkout_data.loop);
+
+ g_main_loop_unref (checkout_data.loop);
+
+ if (checkout_data.caught_error)
+ goto out;
+
+ ret = TRUE;
+ ot_transfer_out_value (out_deployment_path, &deploy_target_path);
+ out:
+ return ret;
+}
+
+static gboolean
+merge_configuration (GFile *sysroot,
+ OtDeployment *previous_deployment,
+ OtDeployment *deployment,
+ GFile *deployment_path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *source_etc_path = NULL;
+ gs_unref_object GFile *source_etc_pristine_path = NULL;
+ gs_unref_object GFile *deployment_usretc_path = NULL;
+ gs_unref_object GFile *deployment_etc_path = NULL;
+ gboolean etc_exists;
+ gboolean usretc_exists;
+
+ if (previous_deployment)
+ {
+ gs_unref_object GFile *previous_path = NULL;
+ OtConfigParser *previous_bootconfig;
+
+ previous_path = ot_admin_get_deployment_directory (sysroot, previous_deployment);
+ source_etc_path = g_file_resolve_relative_path (previous_path, "etc");
+ source_etc_pristine_path = g_file_resolve_relative_path (previous_path, "usr/etc");
+
+ previous_bootconfig = ot_deployment_get_bootconfig (previous_deployment);
+ if (previous_bootconfig)
+ {
+ const char *previous_options = ot_config_parser_get (previous_bootconfig, "options");
+ /* Completely overwrite the previous options here; we will extend
+ * them later.
+ */
+ ot_config_parser_set (ot_deployment_get_bootconfig (deployment), "options",
+ previous_options);
+ }
+ }
+
+ deployment_etc_path = g_file_get_child (deployment_path, "etc");
+ deployment_usretc_path = g_file_resolve_relative_path (deployment_path, "usr/etc");
+
+ etc_exists = g_file_query_exists (deployment_etc_path, NULL);
+ usretc_exists = g_file_query_exists (deployment_usretc_path, NULL);
+
+ if (etc_exists && usretc_exists)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Tree contains both /etc and /usr/etc");
+ goto out;
+ }
+ else if (etc_exists)
+ {
+ /* Compatibility hack */
+ if (!gs_file_rename (deployment_etc_path, deployment_usretc_path,
+ cancellable, error))
+ goto out;
+ usretc_exists = TRUE;
+ etc_exists = FALSE;
+ }
+
+ if (usretc_exists)
+ {
+ g_assert (!etc_exists);
+ if (!gs_shutil_cp_a (deployment_usretc_path, deployment_etc_path,
+ cancellable, error))
+ goto out;
+ g_print ("ostadmin: Created %s\n", gs_file_get_path_cached (deployment_etc_path));
+ }
+
+ if (source_etc_path)
+ {
+ if (!merge_etc_changes (source_etc_pristine_path, source_etc_path, deployment_etc_path,
+ cancellable, error))
+ goto out;
+ }
+ else
+ {
+ g_print ("ostadmin: No previous configuration changes to merge\n");
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+write_origin_file (GFile *sysroot,
+ OtDeployment *deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GKeyFile *origin = ot_deployment_get_origin (deployment);
+
+ if (origin)
+ {
+ gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+ gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+ gs_free char *contents = NULL;
+ gsize len;
+
+ contents = g_key_file_to_data (origin, &len, error);
+ if (!contents)
+ goto out;
+
+ if (!g_file_replace_contents (origin_path, contents, len, NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION, NULL,
+ cancellable, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+get_kernel_from_tree (GFile *deployroot,
+ GFile **out_kernel,
+ GFile **out_initramfs,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *bootdir = g_file_get_child (deployroot, "boot");
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ gs_unref_object GFileInfo *file_info = NULL;
+ gs_unref_object GFile *ret_kernel = NULL;
+ gs_unref_object GFile *ret_initramfs = NULL;
+
+ dir_enum = g_file_enumerate_children (bootdir, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, error);
+ if (!dir_enum)
+ goto out;
+
+ while (TRUE)
+ {
+ GFileInfo *file_info = NULL;
+ const char *name;
+
+ if (!gs_file_enumerator_iterate (dir_enum, &file_info, NULL,
+ cancellable, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ name = g_file_info_get_name (file_info);
+
+ if (ret_kernel == NULL && g_str_has_prefix (name, "vmlinuz-"))
+ ret_kernel = g_file_get_child (bootdir, name);
+ else if (ret_initramfs == NULL && g_str_has_prefix (name, "initramfs-"))
+ ret_initramfs = g_file_get_child (bootdir, name);
+
+ if (ret_kernel && ret_initramfs)
+ break;
+ }
+
+ if (ret_kernel == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+ "Failed to find boot/vmlinuz-CHECKSUM in %s",
+ gs_file_get_path_cached (deployroot));
+ goto out;
+ }
+
+ ot_transfer_out_value (out_kernel, &ret_kernel);
+ ot_transfer_out_value (out_initramfs, &ret_initramfs);
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+checksum_from_kernel_src (GFile *src,
+ char **out_checksum,
+ GError **error)
+{
+ const char *last_dash = strrchr (gs_file_get_path_cached (src), '-');
+ if (!last_dash)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Malformed initramfs name '%s', missing '-'", gs_file_get_basename_cached (src));
+ return FALSE;
+ }
+ *out_checksum = g_strdup (last_dash + 1);
+ return TRUE;
+}
+
+static int
+sort_by_bootserial (gconstpointer ap, gconstpointer bp)
+{
+ OtDeployment **a_loc = (OtDeployment**)ap;
+ OtDeployment *a = *a_loc;
+ OtDeployment **b_loc = (OtDeployment**)bp;
+ OtDeployment *b = *b_loc;
+
+ if (ot_deployment_get_bootserial (a) == ot_deployment_get_bootserial (b))
+ return 0;
+ else if (ot_deployment_get_bootserial (a) < ot_deployment_get_bootserial (b))
+ return -1;
+ return 1;
+}
+
+static GPtrArray *
+filter_deployments_by_bootcsum (GPtrArray *deployments,
+ const char *osname,
+ const char *bootcsum)
+{
+ GPtrArray *ret = g_ptr_array_new ();
+ guint i;
+
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+
+ if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+ continue;
+ if (strcmp (ot_deployment_get_bootcsum (deployment), bootcsum) != 0)
+ continue;
+
+ g_ptr_array_add (ret, deployment);
+ }
+ g_ptr_array_sort (ret, sort_by_bootserial);
+
+ return ret;
+}
+
+static void
+compute_new_deployment_list (int current_bootversion,
+ GPtrArray *current_deployments,
+ const char *osname,
+ OtDeployment *booted_deployment,
+ OtDeployment *merge_deployment,
+ gboolean retain,
+ const char *revision,
+ const char *bootcsum,
+ GPtrArray **out_new_deployments,
+ int *out_new_bootversion)
+{
+ guint i;
+ int new_index;
+ guint new_deployserial = 0;
+ int new_bootserial = 0;
+ gs_unref_object OtDeployment *new_deployment = NULL;
+ gs_unref_ptrarray GPtrArray *matching_deployments_by_bootserial = NULL;
+ OtDeployment *deployment_to_delete = NULL;
+ gs_unref_ptrarray GPtrArray *ret_new_deployments = NULL;
+ gboolean requires_new_bootversion;
+
+ if (osname == NULL)
+ osname = ot_deployment_get_osname (booted_deployment);
+
+ /* First, compute the serial for this deployment; we look
+ * for other ones in this os with the same checksum.
+ */
+ for (i = 0; i < current_deployments->len; i++)
+ {
+ OtDeployment *deployment = current_deployments->pdata[i];
+
+ if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+ continue;
+ if (strcmp (ot_deployment_get_csum (deployment), revision) != 0)
+ continue;
+
+ new_deployserial = MAX(new_deployserial, ot_deployment_get_deployserial (deployment)+1);
+ }
+
+ /* We retain by default (well, hardcoded now) one previous
+ * deployment for this OS, plus the booted deployment. Usually, we
+ * have one previous, one into which we're booted, and we're
+ * deploying a new one. So the old previous will get swapped out,
+ * and booted becomes previous.
+ *
+ * But if the user then upgrades again, we will end up pruning the
+ * front of the deployment list. We never delete the running
+ * deployment.
+ */
+ if (!retain)
+ {
+ for (i = 0; i < current_deployments->len; i++)
+ {
+ OtDeployment *deployment = current_deployments->pdata[i];
+
+ if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+ continue;
+
+ // Keep both the booted and merge deployments
+ if (ot_deployment_equal (deployment, booted_deployment) ||
+ ot_deployment_equal (deployment, merge_deployment))
+ continue;
+
+ deployment_to_delete = deployment;
+ }
+ }
+
+ /* We need to update the bootloader only if the deployment we're
+ * removing uses a different kernel.
+ */
+ requires_new_bootversion =
+ (deployment_to_delete == NULL) ||
+ (strcmp (ot_deployment_get_bootcsum (deployment_to_delete), bootcsum) != 0);
+
+ ret_new_deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
+
+ new_deployment = ot_deployment_new (0, osname, revision, new_deployserial,
+ bootcsum, new_bootserial);
+ g_ptr_array_add (ret_new_deployments, g_object_ref (new_deployment));
+ new_index = 1;
+ for (i = 0; i < current_deployments->len; i++)
+ {
+ OtDeployment *orig_deployment = current_deployments->pdata[i];
+ gs_unref_object OtDeployment *deployment_clone = NULL;
+
+ if (orig_deployment == deployment_to_delete)
+ continue;
+
+ deployment_clone = ot_deployment_clone (orig_deployment);
+ ot_deployment_set_index (deployment_clone, new_index);
+ new_index++;
+ g_ptr_array_add (ret_new_deployments, g_object_ref (deployment_clone));
+ }
+
+ /* Just renumber the deployments for the OS we're adding; we don't
+ * handle anything else at the moment.
+ */
+ matching_deployments_by_bootserial = filter_deployments_by_bootcsum (ret_new_deployments,
+ osname, bootcsum);
+ for (i = 0; i < matching_deployments_by_bootserial->len; i++)
+ {
+ OtDeployment *deployment = matching_deployments_by_bootserial->pdata[i];
+ ot_deployment_set_bootserial (deployment, i);
+ }
+
+ *out_new_deployments = ret_new_deployments;
+ ret_new_deployments = NULL;
+ g_assert (current_bootversion == 0 || current_bootversion == 1);
+ if (requires_new_bootversion)
+ *out_new_bootversion = (current_bootversion == 0) ? 1 : 0;
+ else
+ *out_new_bootversion = current_bootversion;
+}
+
+static GHashTable *
+object_array_to_set (GPtrArray *objlist,
+ GHashFunc hashfunc,
+ GEqualFunc equalfunc)
+{
+ GHashTable *ret = g_hash_table_new_full (hashfunc, equalfunc, g_object_unref, NULL);
+ guint i;
+
+ for (i = 0; i < objlist->len; i++)
+ {
+ GObject *obj = g_object_ref (objlist->pdata[i]);
+ g_hash_table_insert (ret, obj, obj);
+ }
+
+ return ret;
+}
+
+static GHashTable *
+object_set_subtract (GHashTable *a, GHashTable *b)
+{
+ GHashTable *ret = g_hash_table_new_full (NULL, NULL, g_object_unref, NULL);
+ GHashTableIter hashiter;
+ gpointer hashkey, hashvalue;
+
+ g_hash_table_iter_init (&hashiter, a);
+ while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+ {
+ if (!g_hash_table_contains (b, hashkey))
+ {
+ GObject *o = g_object_ref (hashkey);
+ g_hash_table_insert (ret, o, o);
+ }
+ }
+
+ return ret;
+}
+
+static void
+print_deployment_set (gboolean for_removal,
+ GHashTable *set)
+{
+ GHashTableIter hashiter;
+ gpointer hashkey, hashvalue;
+
+ if (g_hash_table_size (set) == 0)
+ return;
+
+ g_print ("%s\n", for_removal ? "removed:" : "added: ");
+
+ g_hash_table_iter_init (&hashiter, set);
+ while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+ {
+ OtDeployment *deployment = hashkey;
+
+ g_print (" %c %s %s.%d",
+ for_removal ? '-' : '+', ot_deployment_get_osname (deployment),
+ ot_deployment_get_csum (deployment),
+ ot_deployment_get_deployserial (deployment));
+
+ if (!for_removal)
+ g_print (" index=%d", ot_deployment_get_index (deployment));
+ g_print ("\n");
+ }
+}
+
+static void
+print_deployment_diff (GPtrArray *current_deployments,
+ GPtrArray *new_deployments)
+{
+ gs_unref_hashtable GHashTable *curset = object_array_to_set (current_deployments, ot_deployment_hash, ot_deployment_equal);
+ gs_unref_hashtable GHashTable *newset = object_array_to_set (new_deployments, ot_deployment_hash, ot_deployment_equal);
+ gs_unref_hashtable GHashTable *removed = NULL;
+ gs_unref_hashtable GHashTable *added = NULL;
+
+ removed = object_set_subtract (curset, newset);
+ added = object_set_subtract (newset, curset);
+
+ print_deployment_set (TRUE, removed);
+ print_deployment_set (FALSE, added);
+}
+
+/* FIXME: We should really do individual fdatasync() on files/dirs,
+ * since this causes us to block on unrelated I/O. However, it's just
+ * safer for now.
+ */
+static gboolean
+full_system_sync (GCancellable *cancellable,
+ GError **error)
+{
+ sync ();
+ return TRUE;
+}
+
+static gboolean
+swap_bootlinks (GFile *sysroot,
+ int current_bootversion,
+ GPtrArray *new_deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint i;
+ int old_subbootversion, new_subbootversion;
+ gs_unref_object GFile *ostree_dir = g_file_get_child (sysroot, "ostree");
+ gs_free char *ostree_bootdir_name = g_strdup_printf ("boot.%d", current_bootversion);
+ gs_unref_object GFile *ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
+ gs_free char *ostree_subbootdir_name = NULL;
+ gs_unref_object GFile *ostree_subbootdir = NULL;
+ gs_unref_ptrarray GPtrArray *deployments_to_swap = NULL;
+
+ if (!ot_admin_read_current_subbootversion (sysroot, current_bootversion,
+ &old_subbootversion,
+ cancellable, error))
+ goto out;
+
+ new_subbootversion = old_subbootversion == 0 ? 1 : 0;
+
+ ostree_subbootdir_name = g_strdup_printf ("boot.%d.%d", current_bootversion, new_subbootversion);
+ ostree_subbootdir = g_file_resolve_relative_path (ostree_dir, ostree_subbootdir_name);
+
+ if (!gs_file_ensure_directory (ostree_subbootdir, TRUE, cancellable, error))
+ goto out;
+
+ for (i = 0; i < new_deployments->len; i++)
+ {
+ OtDeployment *deployment = new_deployments->pdata[i];
+ gs_free char *bootlink_pathname = g_strdup_printf ("%s/%s/%d",
+ ot_deployment_get_osname (deployment),
+ ot_deployment_get_bootcsum (deployment),
+ ot_deployment_get_bootserial (deployment));
+ gs_free char *bootlink_target = g_strdup_printf ("../../../deploy/%s/deploy/%s.%d",
+ ot_deployment_get_osname (deployment),
+ ot_deployment_get_csum (deployment),
+ ot_deployment_get_deployserial (deployment));
+ gs_unref_object GFile *linkname = g_file_get_child (ostree_subbootdir, bootlink_pathname);
+ gs_unref_object GFile *linkname_parent = g_file_get_parent (linkname);
+
+ if (!gs_file_ensure_directory (linkname_parent, TRUE, cancellable, error))
+ goto out;
+
+ if (!g_file_make_symbolic_link (linkname, bootlink_target, cancellable, error))
+ goto out;
+ }
+
+ if (!ot_gfile_atomic_symlink_swap (ostree_bootdir, ostree_subbootdir_name,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static char *
+remove_checksum_from_kernel_name (const char *name,
+ const char *csum)
+{
+ const char *p = strrchr (name, '-');
+ g_assert_cmpstr (p+1, ==, csum);
+ return g_strndup (name, p-name);
+}
+
+static GHashTable *
+parse_os_release (const char *contents,
+ const char *split)
+{
+ char **lines = g_strsplit (contents, split, -1);
+ char **iter;
+ GHashTable *ret = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ for (iter = lines; *iter; iter++)
+ {
+ char *line = *iter;
+ char *eq;
+ const char *quotedval;
+ char *val;
+
+ if (g_str_has_prefix (line, "#"))
+ continue;
+
+ eq = strchr (line, '=');
+ if (!eq)
+ continue;
+
+ *eq = '\0';
+ quotedval = eq + 1;
+ val = g_shell_unquote (quotedval, NULL);
+ if (!val)
+ continue;
+
+ g_hash_table_insert (ret, line, val);
+ }
+
+ return ret;
+}
+
+static gboolean
+install_deployment_kernel (GFile *sysroot,
+ int new_bootversion,
+ OtDeployment *deployment,
+ GCancellable *cancellable,
+ GError **error)
+
+{
+ gboolean ret = FALSE;
+ const char *osname = ot_deployment_get_osname (deployment);
+ const char *bootcsum = ot_deployment_get_bootcsum (deployment);
+ gs_unref_object GFile *bootdir = NULL;
+ gs_unref_object GFile *bootcsumdir = NULL;
+ gs_unref_object GFile *bootconfpath = NULL;
+ gs_unref_object GFile *bootconfpath_parent = NULL;
+ gs_free char *dest_kernel_name = NULL;
+ gs_unref_object GFile *dest_kernel_path = NULL;
+ gs_unref_object GFile *dest_initramfs_path = NULL;
+ gs_unref_object GFile *tree_kernel_path = NULL;
+ gs_unref_object GFile *tree_initramfs_path = NULL;
+ gs_unref_object GFile *etc_os_release = NULL;
+ gs_unref_object GFile *deployment_dir = NULL;
+ gs_free char *contents = NULL;
+ gs_unref_hashtable GHashTable *osrelease_values = NULL;
+ gs_free char *linux_relpath = NULL;
+ gs_free char *linux_key = NULL;
+ gs_free char *initramfs_relpath = NULL;
+ gs_free char *title_key = NULL;
+ gs_free char *initrd_key = NULL;
+ gs_free char *version_key = NULL;
+ gs_free char *ostree_kernel_arg = NULL;
+ gs_free char *options_key = NULL;
+ __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *ohash = NULL;
+ const char *val;
+ OtConfigParser *bootconfig;
+ gsize len;
+
+ bootconfig = ot_deployment_get_bootconfig (deployment);
+ deployment_dir = ot_admin_get_deployment_directory (sysroot, deployment);
+
+ if (!get_kernel_from_tree (deployment_dir, &tree_kernel_path, &tree_initramfs_path,
+ cancellable, error))
+ goto out;
+
+ bootdir = g_file_get_child (sysroot, "boot");
+ bootcsumdir = ot_gfile_resolve_path_printf (bootdir, "ostree/%s-%s",
+ osname,
+ bootcsum);
+ bootconfpath = ot_gfile_resolve_path_printf (bootdir, "loader.%d/entries/ostree-%s-%s-%d.conf",
+ new_bootversion, osname,
+ ot_deployment_get_csum (deployment),
+ ot_deployment_get_bootserial (deployment));
+
+ if (!gs_file_ensure_directory (bootcsumdir, TRUE, cancellable, error))
+ goto out;
+ bootconfpath_parent = g_file_get_parent (bootconfpath);
+ if (!gs_file_ensure_directory (bootconfpath_parent, TRUE, cancellable, error))
+ goto out;
+
+ dest_kernel_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached (tree_kernel_path),
+ bootcsum);
+ dest_kernel_path = g_file_get_child (bootcsumdir, dest_kernel_name);
+ if (!g_file_query_exists (dest_kernel_path, NULL))
+ {
+ if (!gs_file_linkcopy_sync_data (tree_kernel_path, dest_kernel_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+ cancellable, error))
+ goto out;
+ }
+
+ if (tree_initramfs_path)
+ {
+ gs_free char *dest_initramfs_name = remove_checksum_from_kernel_name (gs_file_get_basename_cached (tree_initramfs_path),
+ bootcsum);
+ dest_initramfs_path = g_file_get_child (bootcsumdir, dest_initramfs_name);
+
+ if (!g_file_query_exists (dest_initramfs_path, NULL))
+ {
+ if (!gs_file_linkcopy_sync_data (tree_initramfs_path, dest_initramfs_path, G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_ALL_METADATA,
+ cancellable, error))
+ goto out;
+ }
+ }
+
+ etc_os_release = g_file_resolve_relative_path (deployment_dir, "etc/os-release");
+
+ if (!g_file_load_contents (etc_os_release, cancellable,
+ &contents, &len, NULL, error))
+ {
+ g_prefix_error (error, "Reading /etc/os-release: ");
+ goto out;
+ }
+
+ osrelease_values = parse_os_release (contents, "\n");
+
+ /* title */
+ val = g_hash_table_lookup (osrelease_values, "PRETTY_NAME");
+ if (val == NULL)
+ val = g_hash_table_lookup (osrelease_values, "ID");
+ if (val == NULL)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No PRETTY_NAME or ID in /etc/os-release");
+ goto out;
+ }
+
+ title_key = g_strdup_printf ("ostree:%s:%d %s", ot_deployment_get_osname (deployment),
+ ot_deployment_get_index (deployment),
+ val);
+ ot_config_parser_set (bootconfig, "title", title_key);
+
+ version_key = g_strdup_printf ("%d", ot_deployment_get_bootserial (deployment));
+ ot_config_parser_set (bootconfig, "version", version_key);
+
+ linux_relpath = g_file_get_relative_path (bootdir, dest_kernel_path);
+ linux_key = g_strconcat ("/", linux_relpath, NULL);
+ ot_config_parser_set (bootconfig, "linux", linux_key);
+
+ if (dest_initramfs_path)
+ {
+ initramfs_relpath = g_file_get_relative_path (bootdir, dest_initramfs_path);
+ initrd_key = g_strconcat ("/", initramfs_relpath, NULL);
+ ot_config_parser_set (bootconfig, "initrd", initrd_key);
+ }
+
+ val = ot_config_parser_get (bootconfig, "options");
+ ostree_kernel_arg = g_strdup_printf ("/ostree/boot.%d/%s/%s/%d",
+ new_bootversion, osname, bootcsum,
+ ot_deployment_get_bootserial (deployment));
+ ohash = ot_admin_parse_kernel_args (val);
+ ot_ordered_hash_replace_key (ohash, "ostree", ostree_kernel_arg);
+ options_key = ot_admin_kernel_arg_string_serialize (ohash);
+ ot_config_parser_set (bootconfig, "options", options_key);
+
+ if (!ot_config_parser_write (ot_deployment_get_bootconfig (deployment), bootconfpath,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+swap_bootloader (GFile *sysroot,
+ int current_bootversion,
+ int new_bootversion,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *boot_loader_link = NULL;
+ gs_free char *new_target = NULL;
+
+ g_assert ((current_bootversion == 0 && new_bootversion == 1) ||
+ (current_bootversion == 1 && new_bootversion == 0));
+
+ boot_loader_link = g_file_resolve_relative_path (sysroot, "boot/loader");
+ new_target = g_strdup_printf ("loader.%d", new_bootversion);
+
+ if (!ot_gfile_atomic_symlink_swap (boot_loader_link, new_target,
+ cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+ot_admin_deploy (GFile *sysroot,
+ int current_bootversion,
+ GPtrArray *current_deployments,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ char **add_kernel_argv,
+ gboolean retain,
+ OtDeployment *booted_deployment,
+ OtDeployment *provided_merge_deployment,
+ OtDeployment **out_new_deployment,
+ int *out_new_bootversion,
+ GPtrArray **out_new_deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ OtDeployment *new_deployment;
+ gs_unref_object OtDeployment *merge_deployment = NULL;
+ gs_unref_object OtBootloader *bootloader = NULL;
+ gs_unref_object GFile *rootfs = NULL;
+ gs_unref_object OstreeRepo *repo = NULL;
+ gs_unref_object GFile *commit_root = NULL;
+ gs_unref_object GFile *tree_kernel_path = NULL;
+ gs_unref_object GFile *tree_initramfs_path = NULL;
+ gs_unref_object GFile *new_deployment_path = NULL;
+ gs_unref_object GFile *deploy_path = NULL;
+ gs_unref_object GFile *osdir = NULL;
+ gs_free char *new_bootcsum = NULL;
+ gs_unref_object GFile *source_etc_path = NULL;
+ gs_unref_object GFile *source_etc_pristine_path = NULL;
+ gs_unref_object OtConfigParser *bootconfig = NULL;
+ gs_free char *source_etc_kernel_args = NULL;
+ gs_unref_ptrarray GPtrArray *new_deployments = NULL;
+ int new_bootversion;
+ int i;
+
+ if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
+ goto out;
+
+ /* Here we perform cleanup of any leftover data from previous
+ * partial failures. This avoids having to call gs_shutil_rm_rf()
+ * at random points throughout the process.
+ *
+ * TODO: Add /ostree/transaction file, and only do this cleanup if
+ * we find it.
+ */
+ if (!ot_admin_cleanup (sysroot, cancellable, error))
+ {
+ g_prefix_error (error, "Performing initial cleanup: ");
+ goto out;
+ }
+
+ if (!ostree_repo_read_commit (repo, revision, &commit_root, cancellable, error))
+ goto out;
+
+ if (!get_kernel_from_tree (commit_root, &tree_kernel_path, &tree_initramfs_path,
+ cancellable, error))
+ goto out;
+
+ if (tree_initramfs_path != NULL)
+ {
+ if (!checksum_from_kernel_src (tree_initramfs_path, &new_bootcsum, error))
+ goto out;
+ }
+ else
+ {
+ if (!checksum_from_kernel_src (tree_kernel_path, &new_bootcsum, error))
+ goto out;
+ }
+
+ bootloader = ot_admin_query_bootloader (sysroot);
+ if (!bootloader)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No known bootloader configuration detected");
+ goto out;
+ }
+
+ /* If we're booted into the OS into which we're deploying, then
+ * merge the currently *booted* configuration, rather than the most
+ * recently deployed.
+ */
+ if (provided_merge_deployment != NULL)
+ merge_deployment = g_object_ref (provided_merge_deployment);
+ else
+ merge_deployment = ot_admin_get_merge_deployment (current_deployments, osname,
+ booted_deployment,
+ new_deployment);
+
+ compute_new_deployment_list (current_bootversion,
+ current_deployments, osname,
+ booted_deployment, merge_deployment,
+ retain,
+ revision, new_bootcsum,
+ &new_deployments,
+ &new_bootversion);
+ new_deployment = g_object_ref (new_deployments->pdata[0]);
+ ot_deployment_set_origin (new_deployment, origin);
+
+ print_deployment_diff (current_deployments, new_deployments);
+
+ /* Check out the userspace tree onto the filesystem */
+ if (!checkout_deployment_tree (sysroot, repo, new_deployment, &new_deployment_path,
+ cancellable, error))
+ {
+ g_prefix_error (error, "Checking out tree: ");
+ goto out;
+ }
+
+ if (!write_origin_file (sysroot, new_deployment, cancellable, error))
+ {
+ g_prefix_error (error, "Writing out origin file: ");
+ goto out;
+ }
+
+ /* Create an empty boot configuration; we will merge things into
+ * it as we go.
+ */
+ bootconfig = ot_config_parser_new (" ");
+ ot_deployment_set_bootconfig (new_deployment, bootconfig);
+
+ if (!merge_configuration (sysroot, merge_deployment, new_deployment,
+ new_deployment_path,
+ cancellable, error))
+ {
+ g_prefix_error (error, "During /etc merge: ");
+ goto out;
+ }
+
+ /* We have inherited kernel arguments from the previous deployment;
+ * now, override/extend that with arguments provided by the command
+ * line.
+ *
+ * After this, install_deployment_kernel() will set the other boot
+ * options and write it out to disk.
+ */
+ if (add_kernel_argv)
+ {
+ char **strviter;
+ __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *ohash = NULL;
+ gs_free char *new_options = NULL;
+
+ ohash = ot_admin_parse_kernel_args (ot_config_parser_get (bootconfig, "options"));
+
+ for (strviter = add_kernel_argv; *strviter; strviter++)
+ {
+ char *karg = g_strdup (*strviter);
+ const char *val = ot_admin_split_keyeq (karg);
+
+ ot_ordered_hash_replace_key_take (ohash, karg, val);
+ }
+
+ new_options = ot_admin_kernel_arg_string_serialize (ohash);
+ ot_config_parser_set (bootconfig, "options", new_options);
+ }
+
+ if (current_bootversion == new_bootversion)
+ {
+ if (!full_system_sync (cancellable, error))
+ {
+ g_prefix_error (error, "Full sync: ");
+ goto out;
+ }
+
+ if (!swap_bootlinks (sysroot, current_bootversion,
+ new_deployments,
+ cancellable, error))
+ {
+ g_prefix_error (error, "Swapping current bootlinks: ");
+ goto out;
+ }
+ }
+ else
+ {
+ for (i = 0; i < new_deployments->len; i++)
+ {
+ OtDeployment *deployment = new_deployments->pdata[i];
+ if (!install_deployment_kernel (sysroot, new_bootversion, deployment,
+ cancellable, error))
+ {
+ g_prefix_error (error, "Installing kernel: ");
+ goto out;
+ }
+ }
+
+ /* Swap bootlinks for *new* version */
+ if (!swap_bootlinks (sysroot, new_bootversion, new_deployments,
+ cancellable, error))
+ {
+ g_prefix_error (error, "Generating new bootlinks: ");
+ goto out;
+ }
+
+ if (!full_system_sync (cancellable, error))
+ {
+ g_prefix_error (error, "Full sync: ");
+ goto out;
+ }
+
+ if (!ot_bootloader_write_config (bootloader, new_bootversion,
+ cancellable, error))
+ goto out;
+
+ if (!swap_bootloader (sysroot, current_bootversion, new_bootversion,
+ cancellable, error))
+ {
+ g_prefix_error (error, "Final bootloader swap: ");
+ goto out;
+ }
+ }
+
+ /* TEMPORARY HACK: Add a "current" symbolic link that's easy to
+ * follow inside the gnome-ostree build scripts. This isn't atomic,
+ * but that doesn't matter because it's only used by deployments
+ * done from the host.
+ */
+ {
+ gs_unref_object GFile *osdir = ot_gfile_resolve_path_printf (sysroot, "ostree/deploy/%s", ot_deployment_get_osname (new_deployment));
+ gs_unref_object GFile *os_current_path = g_file_get_child (osdir, "current");
+ gs_free char *target = g_file_get_relative_path (osdir, new_deployment_path);
+ g_assert (target != NULL);
+ if (!ot_gfile_atomic_symlink_swap (os_current_path, target,
+ cancellable, error))
+ goto out;
+ }
+
+ /* And finally, cleanup of any leftover data.
+ */
+ if (!ot_admin_cleanup (sysroot, cancellable, error))
+ {
+ g_prefix_error (error, "Performing final cleanup: ");
+ goto out;
+ }
+
+ ret = TRUE;
+ ot_transfer_out_value (out_new_deployment, &new_deployment);
+ *out_new_bootversion = new_bootversion;
+ ot_transfer_out_value (out_new_deployments, &new_deployments)
+ out:
+ return ret;
+}
+
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2012 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Colin Walters <walters@verbum.org>
+ */
+
+#ifndef __OT_ADMIN_DEPLOY__
+#define __OT_ADMIN_DEPLOY_
+
+#include <gio/gio.h>
+#include "ot-deployment.h"
+#include "ot-bootloader.h"
+#include "ot-ordered-hash.h"
+
+G_BEGIN_DECLS
+
+gboolean ot_admin_deploy (GFile *sysroot,
+ int current_bootversion,
+ GPtrArray *current_deployments,
+ const char *osname,
+ const char *revision,
+ GKeyFile *origin,
+ char **add_kernel_argv,
+ gboolean retain,
+ OtDeployment *booted_deployment,
+ OtDeployment *merge_deployment,
+ OtDeployment **out_new_deployment,
+ int *out_new_bootversion,
+ GPtrArray **out_new_deployments,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif
* Author: Colin Walters <walters@verbum.org>
*/
+#define _GNU_SOURCE
#include "config.h"
#include "ot-admin-functions.h"
+#include "ot-deployment.h"
+#include "ot-config-parser.h"
+#include "ot-bootloader-syslinux.h"
#include "otutil.h"
#include "ostree-core.h"
+#include "ostree-prune.h"
+#include "libgsystem.h"
+
+/*
+ * Modify @arg which should be of the form key=value to make @arg just
+ * contain key. Return a pointer to the start of value.
+ */
+char *
+ot_admin_split_keyeq (char *arg)
+{
+ char *eq;
+
+ eq = strchr (arg, '=');
+ if (eq)
+ {
+ /* Note key/val are in one malloc block,
+ * so we don't free val...
+ */
+ *eq = '\0';
+ return eq+1;
+ }
+ else
+ {
+ /* ...and this allows us to insert a constant
+ * string.
+ */
+ return "";
+ }
+}
+
+OtOrderedHash *
+ot_admin_parse_kernel_args (const char *options)
+{
+ OtOrderedHash *ret;
+ char **args;
+ char **iter;
+
+ ret = ot_ordered_hash_new ();
+
+ if (!options)
+ return ret;
+
+ args = g_strsplit (options, " ", -1);
+ for (iter = args; *iter; iter++)
+ {
+ char *arg = *iter;
+ char *val;
+
+ val = ot_admin_split_keyeq (arg);
+
+ g_ptr_array_add (ret->order, arg);
+ g_hash_table_insert (ret->table, arg, val);
+ }
+
+ return ret;
+}
+
+char *
+ot_admin_kernel_arg_string_serialize (OtOrderedHash *ohash)
+{
+ guint i;
+ GString *buf = g_string_new ("");
+ gboolean first = TRUE;
+
+ for (i = 0; i < ohash->order->len; i++)
+ {
+ const char *key = ohash->order->pdata[i];
+ const char *val = g_hash_table_lookup (ohash->table, key);
+
+ g_assert (val != NULL);
+
+ if (first)
+ first = FALSE;
+ else
+ g_string_append_c (buf, ' ');
+
+ if (*val)
+ g_string_append_printf (buf, "%s=%s", key, val);
+ else
+ g_string_append (buf, key);
+ }
+
+ return g_string_free (buf, FALSE);
+}
+
+
+static void
+match_info_cleanup (void *loc)
+{
+ GMatchInfo **match = (GMatchInfo**)loc;
+ if (*match) g_match_info_unref (*match);
+}
gboolean
-ot_admin_ensure_initialized (GFile *ostree_dir,
+ot_admin_ensure_initialized (GFile *sysroot,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
- ot_lobj GFile *dir = NULL;
+ gs_unref_object GFile *dir = NULL;
+ gs_unref_object GFile *ostree_dir = NULL;
+
+ ostree_dir = g_file_get_child (sysroot, "ostree");
g_clear_object (&dir);
dir = g_file_get_child (ostree_dir, "repo");
return ret;
}
+gboolean
+ot_admin_check_os (GFile *sysroot,
+ const char *osname,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *osdir = NULL;
+
+ osdir = ot_gfile_resolve_path_printf (sysroot, "ostree/deploy/%s/var", osname);
+ if (!g_file_query_exists (osdir, NULL))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No such OS '%s', use os-init to create it", osname);
+ goto out;
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
static gboolean
-query_file_info_allow_noent (GFile *path,
- GFileInfo **out_info,
- GCancellable *cancellable,
- GError **error)
+parse_bootlink (const char *bootlink,
+ int *out_entry_bootversion,
+ char **out_osname,
+ char **out_bootcsum,
+ int *out_treebootserial,
+ GError **error)
{
gboolean ret = FALSE;
- ot_lobj GFileInfo *ret_file_info = NULL;
- GError *temp_error = NULL;
+ __attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
+ gs_free char *bootversion_str = NULL;
+ gs_free char *treebootserial_str = NULL;
- ret_file_info = g_file_query_info (path, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, &temp_error);
- if (!ret_file_info)
+ static gsize regex_initialized;
+ static GRegex *regex;
+
+ if (g_once_init_enter (®ex_initialized))
{
- if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ regex = g_regex_new ("^/ostree/boot.([01])/([^/]+)/([^/]+)/([0-9]+)$", 0, 0, NULL);
+ g_assert (regex);
+ g_once_init_leave (®ex_initialized, 1);
+ }
+
+ if (!g_regex_match (regex, bootlink, 0, &match))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid ostree= argument '%s', expected ostree=/ostree/boot.BOOTVERSION/OSNAME/BOOTCSUM/TREESERIAL", bootlink);
+ goto out;
+ }
+
+ bootversion_str = g_match_info_fetch (match, 1);
+ *out_entry_bootversion = (int)g_ascii_strtoll (bootversion_str, NULL, 10);
+ *out_osname = g_match_info_fetch (match, 2);
+ *out_bootcsum = g_match_info_fetch (match, 3);
+ treebootserial_str = g_match_info_fetch (match, 4);
+ *out_treebootserial = (int)g_ascii_strtoll (treebootserial_str, NULL, 10);
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+parse_deploy_path_name (const char *name,
+ char **out_csum,
+ int *out_serial,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ __attribute__((cleanup(match_info_cleanup))) GMatchInfo *match = NULL;
+ gs_free char *serial_str = NULL;
+
+ static gsize regex_initialized;
+ static GRegex *regex;
+
+ if (g_once_init_enter (®ex_initialized))
+ {
+ regex = g_regex_new ("^([0-9a-f]+)\\.([0-9]+)$", 0, 0, NULL);
+ g_assert (regex);
+ g_once_init_leave (®ex_initialized, 1);
+ }
+
+ if (!g_regex_match (regex, name, 0, &match))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid deploy name '%s', expected CHECKSUM.TREESERIAL", name);
+ goto out;
+ }
+
+ *out_csum = g_match_info_fetch (match, 1);
+ serial_str = g_match_info_fetch (match, 2);
+ *out_serial = (int)g_ascii_strtoll (serial_str, NULL, 10);
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+GFile *
+ot_admin_get_deployment_origin_path (GFile *deployment_path)
+{
+ gs_unref_object GFile *deployment_parent = g_file_get_parent (deployment_path);
+ return ot_gfile_resolve_path_printf (deployment_parent,
+ "%s.origin",
+ gs_file_get_path_cached (deployment_path));
+}
+
+static gboolean
+parse_origin (GFile *sysroot,
+ GFile *deployment_path,
+ GKeyFile **out_origin,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GKeyFile *ret_origin = NULL;
+ gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+ gs_free char *origin_contents = NULL;
+
+ if (!ot_gfile_load_contents_utf8_allow_noent (origin_path, &origin_contents,
+ cancellable, error))
+ goto out;
+
+ if (origin_contents)
+ {
+ ret_origin = g_key_file_new ();
+ if (!g_key_file_load_from_data (ret_origin, origin_contents, -1, 0, error))
+ goto out;
+ }
+
+ ret = TRUE;
+ ot_transfer_out_value (out_origin, &ret_origin);
+ out:
+ if (error)
+ g_prefix_error (error, "Parsing %s: ", gs_file_get_path_cached (origin_path));
+ if (ret_origin)
+ g_key_file_unref (ret_origin);
+ return ret;
+}
+
+static gboolean
+parse_deployment (GFile *sysroot,
+ const char *boot_link,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ const char *relative_boot_link;
+ gs_unref_object OtDeployment *ret_deployment = NULL;
+ int entry_boot_version;
+ int treebootserial;
+ int deployserial;
+ gs_free char *osname = NULL;
+ gs_free char *bootcsum = NULL;
+ gs_free char *treecsum = NULL;
+ gs_unref_object GFile *treebootserial_link = NULL;
+ gs_unref_object GFileInfo *treebootserial_info = NULL;
+ gs_unref_object GFile *treebootserial_target = NULL;
+ GKeyFile *origin = NULL;
+
+ if (!parse_bootlink (boot_link, &entry_boot_version,
+ &osname, &bootcsum, &treebootserial,
+ error))
+ goto out;
+
+ relative_boot_link = boot_link;
+ if (*relative_boot_link == '/')
+ relative_boot_link++;
+ treebootserial_link = g_file_resolve_relative_path (sysroot, relative_boot_link);
+ treebootserial_info = g_file_query_info (treebootserial_link, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+ if (!treebootserial_info)
+ goto out;
+
+ if (!ot_gfile_get_symlink_target_from_info (treebootserial_link, treebootserial_info,
+ &treebootserial_target, cancellable, error))
+ goto out;
+
+ if (!parse_deploy_path_name (gs_file_get_basename_cached (treebootserial_target),
+ &treecsum, &deployserial, error))
+ goto out;
+
+ if (!parse_origin (sysroot, treebootserial_target, &origin,
+ cancellable, error))
+ goto out;
+
+ ret_deployment = ot_deployment_new (-1, osname, treecsum, deployserial,
+ bootcsum, treebootserial);
+ if (origin)
+ ot_deployment_set_origin (ret_deployment, origin);
+
+ ret = TRUE;
+ ot_transfer_out_value (out_deployment, &ret_deployment);
+ out:
+ if (origin)
+ g_key_file_unref (origin);
+ return ret;
+}
+
+static gboolean
+parse_kernel_commandline (OtOrderedHash **out_args,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *proc_cmdline = g_file_new_for_path ("/proc/cmdline");
+ gs_free char *contents = NULL;
+ gsize len;
+
+ if (!g_file_load_contents (proc_cmdline, cancellable, &contents, &len, NULL,
+ error))
+ goto out;
+
+ ret = TRUE;
+ *out_args = ot_admin_parse_kernel_args (contents);;
+ out:
+ return ret;
+}
+
+static gboolean
+get_devino (GFile *path,
+ guint32 *out_device,
+ guint64 *out_inode,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFileInfo *finfo = g_file_query_info (path, "unix::device,unix::inode",
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable, error);
+
+ if (!finfo)
+ goto out;
+
+ ret = TRUE;
+ *out_device = g_file_info_get_attribute_uint32 (finfo, "unix::device");
+ *out_inode = g_file_info_get_attribute_uint64 (finfo, "unix::inode");
+ out:
+ return ret;
+}
+
+/**
+ * ot_admin_find_booted_deployment:
+ *
+ * Returns in @out_deployment the currently booted deployment using
+ * the list in @deployments. Will always return %NULL if
+ * @target_sysroot is not equal to "/".
+ */
+gboolean
+ot_admin_find_booted_deployment (GFile *target_sysroot,
+ GPtrArray *deployments,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *active_root = g_file_new_for_path ("/");
+ gs_unref_object OtDeployment *ret_deployment = NULL;
+
+ if (g_file_equal (active_root, target_sysroot))
+ {
+ guint i;
+ const char *bootlink_arg;
+ __attribute__((cleanup(ot_ordered_hash_cleanup))) OtOrderedHash *kernel_args = NULL;
+ guint32 root_device;
+ guint64 root_inode;
+
+ if (!get_devino (active_root, &root_device, &root_inode,
+ cancellable, error))
+ goto out;
+
+ if (!parse_kernel_commandline (&kernel_args, cancellable, error))
+ goto out;
+
+ bootlink_arg = g_hash_table_lookup (kernel_args->table, "ostree");
+ if (bootlink_arg)
{
- g_clear_error (&temp_error);
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+ gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (active_root, deployment);
+ guint32 device;
+ guint64 inode;
+
+ if (!get_devino (deployment_path, &device, &inode,
+ cancellable, error))
+ goto out;
+
+ if (device == root_device && inode == root_inode)
+ {
+ ret_deployment = g_object_ref (deployment);
+ break;
+ }
+ }
+ if (ret_deployment == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unexpected state: ostree= kernel argument found, but / is not a deployment root");
+ goto out;
+ }
}
else
{
- g_propagate_error (error, temp_error);
- goto out;
+ /* Not an ostree system */
}
}
ret = TRUE;
- ot_transfer_out_value (out_info, &ret_file_info);
+ ot_transfer_out_value (out_deployment, &ret_deployment);
out:
return ret;
}
-static gboolean
-query_symlink_target_allow_noent (GFile *path,
- GFile **out_target,
- GCancellable *cancellable,
- GError **error)
+gboolean
+ot_admin_require_deployment_or_osname (GFile *sysroot,
+ GPtrArray *deployments,
+ const char *osname,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- ot_lobj GFileInfo *file_info = NULL;
- ot_lobj GFile *ret_target = NULL;
- ot_lobj GFile *path_parent = NULL;
+ gs_unref_object OtDeployment *ret_deployment = NULL;
- if (!query_file_info_allow_noent (path, &file_info,
- cancellable, error))
+ if (!ot_admin_find_booted_deployment (sysroot, deployments, &ret_deployment,
+ cancellable, error))
goto out;
- path_parent = g_file_get_parent (path);
+ if (ret_deployment == NULL && osname == NULL)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not currently booted into an OSTree system and no --os= argument given");
+ goto out;
+ }
+
+ ret = TRUE;
+ ot_transfer_out_value (out_deployment, &ret_deployment);
+ out:
+ return ret;
+}
+
+OtDeployment *
+ot_admin_get_merge_deployment (GPtrArray *deployments,
+ const char *osname,
+ OtDeployment *booted_deployment,
+ OtDeployment *new_deployment)
+{
+ g_return_val_if_fail (osname != NULL || booted_deployment != NULL, NULL);
- if (file_info != NULL)
+ if (osname == NULL)
+ osname = ot_deployment_get_osname (booted_deployment);
+
+ if (booted_deployment &&
+ new_deployment &&
+ g_strcmp0 (ot_deployment_get_osname (booted_deployment),
+ ot_deployment_get_osname (new_deployment)) == 0)
{
- const char *target;
-
- if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_SYMBOLIC_LINK)
+ return g_object_ref (booted_deployment);
+ }
+ else
+ {
+ guint i;
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+
+ if (strcmp (ot_deployment_get_osname (deployment), osname) != 0)
+ continue;
+ if (deployment == new_deployment)
+ continue;
+
+ return g_object_ref (deployment);
+ }
+ }
+ return NULL;
+}
+
+static gboolean
+read_current_bootversion (GFile *sysroot,
+ int *out_bootversion,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *boot_loader_path = g_file_resolve_relative_path (sysroot, "boot/loader");
+ gs_unref_object GFileInfo *info = NULL;
+ const char *target;
+ int ret_bootversion;
+
+ if (!ot_gfile_query_info_allow_noent (boot_loader_path, OSTREE_GIO_FAST_QUERYINFO,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ &info,
+ cancellable, error))
+ goto out;
+
+ if (info == NULL)
+ ret_bootversion = 0;
+ else
+ {
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_SYMBOLIC_LINK)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Not a symbolic link: %s", gs_file_get_path_cached (boot_loader_path));
+ goto out;
+ }
+
+ target = g_file_info_get_symlink_target (info);
+ if (g_strcmp0 (target, "loader.0") == 0)
+ ret_bootversion = 0;
+ else if (g_strcmp0 (target, "loader.1") == 0)
+ ret_bootversion = 1;
+ else
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
- "Not a symbolic link");
+ "Invalid target '%s' in %s", target, gs_file_get_path_cached (boot_loader_path));
goto out;
}
- target = g_file_info_get_symlink_target (file_info);
- g_assert (target);
- ret_target = g_file_resolve_relative_path (path_parent, target);
}
ret = TRUE;
- ot_transfer_out_value (out_target, &ret_target);
+ *out_bootversion = ret_bootversion;
out:
return ret;
}
-/**
- * ot_admin_get_current_deployment:
- *
- * Returns in @out_deployment the full file path of the current
- * deployment that the /ostree/current symbolic link points to, or
- * %NULL if none.
- */
gboolean
-ot_admin_get_current_deployment (GFile *ostree_dir,
- const char *osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error)
+ot_admin_read_current_subbootversion (GFile *sysroot,
+ int bootversion,
+ int *out_subbootversion,
+ GCancellable *cancellable,
+ GError **error)
{
- ot_lobj GFile *current_path = NULL;
+ gboolean ret = FALSE;
+ gs_unref_object GFile *ostree_dir = g_file_get_child (sysroot, "ostree");
+ gs_free char *ostree_bootdir_name = g_strdup_printf ("boot.%d", bootversion);
+ gs_unref_object GFile *ostree_bootdir = g_file_resolve_relative_path (ostree_dir, ostree_bootdir_name);
+ gs_free char *ostree_subbootdir_name = NULL;
+ gs_unref_object GFile *ostree_subbootdir = NULL;
+ gs_unref_ptrarray GPtrArray *deployments_to_swap = NULL;
- current_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
- "current", NULL);
+ if (!ot_gfile_query_symlink_target_allow_noent (ostree_bootdir, &ostree_subbootdir,
+ cancellable, error))
+ goto out;
- return query_symlink_target_allow_noent (current_path, out_deployment,
- cancellable, error);
+ if (ostree_subbootdir == NULL)
+ {
+ *out_subbootversion = 0;
+ }
+ else
+ {
+ const char *current_subbootdir_name = gs_file_get_basename_cached (ostree_subbootdir);
+ if (g_str_has_suffix (current_subbootdir_name, ".0"))
+ *out_subbootversion = 0;
+ else if (g_str_has_suffix (current_subbootdir_name, ".1"))
+ *out_subbootversion = 1;
+ else
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid target '%s' in %s",
+ gs_file_get_path_cached (ostree_subbootdir),
+ gs_file_get_path_cached (ostree_bootdir));
+ goto out;
+ }
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
}
-/**
- * ot_admin_get_previous_deployment:
- *
- * Returns in @out_deployment the full file path of the current
- * deployment that the /ostree/previous symbolic link points to, or
- * %NULL if none.
- */
gboolean
-ot_admin_get_previous_deployment (GFile *ostree_dir,
- const char *osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error)
+ot_admin_read_boot_loader_configs (GFile *sysroot,
+ int bootversion,
+ GPtrArray **out_loader_configs,
+ GCancellable *cancellable,
+ GError **error)
{
- ot_lobj GFile *previous_path = NULL;
+ gboolean ret = FALSE;
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ gs_unref_object GFile *loader_entries_dir = NULL;
+ gs_unref_ptrarray GPtrArray *ret_loader_configs = NULL;
+ GError *temp_error = NULL;
- previous_path = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname,
- "previous", NULL);
+ loader_entries_dir = ot_gfile_resolve_path_printf (sysroot, "boot/loader.%d/entries",
+ bootversion);
+ ret_loader_configs = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
- return query_symlink_target_allow_noent (previous_path, out_deployment,
- cancellable, error);
+ dir_enum = g_file_enumerate_children (loader_entries_dir, OSTREE_GIO_FAST_QUERYINFO,
+ 0, NULL, &temp_error);
+ if (!dir_enum)
+ {
+ if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_clear_error (&temp_error);
+ goto done;
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
+
+ while (TRUE)
+ {
+ GFileInfo *file_info;
+ GFile *child;
+ const char *name;
+
+ if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+ cancellable, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ name = g_file_info_get_name (file_info);
+
+ if (g_str_has_prefix (name, "ostree-") &&
+ g_str_has_suffix (name, ".conf") &&
+ g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
+ {
+ gs_unref_object OtConfigParser *config = ot_config_parser_new (" \t");
+
+ if (!ot_config_parser_parse (config, child, cancellable, error))
+ {
+ g_prefix_error (error, "Parsing %s: ", gs_file_get_path_cached (child));
+ goto out;
+ }
+
+ g_ptr_array_add (ret_loader_configs, g_object_ref (config));
+ }
+ }
+
+ done:
+ ot_transfer_out_value (out_loader_configs, &ret_loader_configs);
+ ret = TRUE;
+ out:
+ return ret;
}
-/*
static gboolean
-ot_admin_list_osnames (GFile *ostree_dir,
- GPtrArray **out_osnames,
- GCancellable *cancellable,
- GError **error)
+list_deployment_dirs_for_os (GFile *osdir,
+ GPtrArray *inout_deployments,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- ot_lobj GFileEnumerator *dir_enum = NULL;
- ot_lobj GFileInfo *file_info = NULL;
- ot_lobj GFile *deploy_dir = NULL;
- ot_lptrarray GPtrArray *ret_osnames = NULL;
+ const char *osname = gs_file_get_basename_cached (osdir);
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ gs_unref_object GFile *osdeploy_dir = NULL;
GError *temp_error = NULL;
- deploy_dir = g_file_get_child (ostree_dir, "deploy");
+ osdeploy_dir = g_file_get_child (osdir, "deploy");
- dir_enum = g_file_enumerate_children (deploy_dir, OSTREE_GIO_FAST_QUERYINFO,
+ dir_enum = g_file_enumerate_children (osdeploy_dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL, error);
+ NULL, &temp_error);
if (!dir_enum)
- goto out;
-
- while ((file_info = g_file_enumerator_next_file (dir_enum, NULL, error)) != NULL)
{
- if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+ if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
- char *name = g_strdup (g_file_info_get_name (file_info));
- g_ptr_array_add (ret_osnames, name);
+ g_clear_error (&temp_error);
+ goto done;
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
}
- g_clear_object (&file_info);
}
- if (temp_error != NULL)
+ while (TRUE)
{
- g_propagate_error (error, temp_error);
- goto out;
+ const char *name;
+ GFileInfo *file_info = NULL;
+ GFile *child = NULL;
+ gs_unref_object OtDeployment *deployment = NULL;
+ gs_free char *csum = NULL;
+ gint deployserial;
+
+ if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+ cancellable, error))
+ goto out;
+ if (file_info == NULL)
+ break;
+
+ name = g_file_info_get_name (file_info);
+
+ if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
+ continue;
+
+ if (!parse_deploy_path_name (name, &csum, &deployserial, error))
+ goto out;
+
+ deployment = ot_deployment_new (-1, osname, csum, deployserial, NULL, -1);
+ g_ptr_array_add (inout_deployments, g_object_ref (deployment));
}
-
+
+ done:
ret = TRUE;
- ot_transfer_out_value (out_osnames, &ret_osnames);
out:
return ret;
}
-*/
static gboolean
-list_deployments_internal (GFile *from_dir,
- GPtrArray *inout_deployments,
- GCancellable *cancellable,
- GError **error)
+list_all_deployment_directories (GFile *sysroot,
+ GPtrArray **out_deployments,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
+ gs_unref_object GFileEnumerator *dir_enum = NULL;
+ gs_unref_object GFile *deploydir = NULL;
+ gs_unref_object GFile *osdir = NULL;
+ gs_unref_ptrarray GPtrArray *ret_deployments = NULL;
GError *temp_error = NULL;
- ot_lobj GFileEnumerator *dir_enum = NULL;
- ot_lobj GFileInfo *file_info = NULL;
- dir_enum = g_file_enumerate_children (from_dir, OSTREE_GIO_FAST_QUERYINFO,
+ deploydir = g_file_resolve_relative_path (sysroot, "ostree/deploy");
+
+ ret_deployments = g_ptr_array_new_with_free_func (g_object_unref);
+
+ dir_enum = g_file_enumerate_children (deploydir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL, error);
+ cancellable, &temp_error);
if (!dir_enum)
- goto out;
+ {
+ if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ g_clear_error (&temp_error);
+ goto done;
+ }
+ else
+ {
+ g_propagate_error (error, temp_error);
+ goto out;
+ }
+ }
- while ((file_info = g_file_enumerator_next_file (dir_enum, cancellable, error)) != NULL)
+ while (TRUE)
{
- const char *name;
- ot_lobj GFile *child = NULL;
- ot_lobj GFile *possible_etc = NULL;
- ot_lobj GFile *possible_usr = NULL;
+ GFileInfo *file_info = NULL;
+ GFile *child = NULL;
- name = g_file_info_get_name (file_info);
+ if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+ NULL, error))
+ goto out;
+ if (file_info == NULL)
+ break;
- if (g_str_has_suffix (name, "-etc"))
- goto next;
if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
- goto next;
+ continue;
+
+ if (!list_deployment_dirs_for_os (child, ret_deployments, cancellable, error))
+ goto out;
+ }
+
+ done:
+ ret = TRUE;
+ ot_transfer_out_value (out_deployments, &ret_deployments);
+ out:
+ return ret;
+}
- child = g_file_get_child (from_dir, name);
+static char *
+get_ostree_kernel_arg_from_config (OtConfigParser *config)
+{
+ const char *options;
+ char *ret;
+ char **opts, **iter;
- possible_etc = ot_gfile_get_child_strconcat (from_dir, name, "-etc", NULL);
- /* Bit of a hack... */
- possible_usr = g_file_get_child (child, "usr");
+ options = ot_config_parser_get (config, "options");
+ if (!options)
+ return NULL;
- if (g_file_query_exists (possible_etc, cancellable))
- g_ptr_array_add (inout_deployments, g_file_get_child (from_dir, name));
- else if (g_file_query_exists (possible_usr, cancellable))
- goto next;
- else
+ opts = g_strsplit (options, " ", -1);
+ for (iter = opts; *iter; iter++)
+ {
+ const char *opt = *iter;
+ if (g_str_has_prefix (opt, "ostree="))
{
- if (!list_deployments_internal (child, inout_deployments,
- cancellable, error))
- goto out;
+ ret = g_strdup (opt + strlen ("ostree="));
+ break;
}
-
- next:
- g_clear_object (&file_info);
}
- if (temp_error != NULL)
+ g_strfreev (opts);
+
+ return ret;
+}
+
+static gboolean
+list_deployments_process_one_boot_entry (GFile *sysroot,
+ OtConfigParser *config,
+ GPtrArray *inout_deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_free char *ostree_arg = NULL;
+ gs_unref_object OtDeployment *deployment = NULL;
+
+ ostree_arg = get_ostree_kernel_arg_from_config (config);
+ if (ostree_arg == NULL)
{
- g_propagate_error (error, temp_error);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No ostree= kernel argument found");
goto out;
}
+
+ if (!parse_deployment (sysroot, ostree_arg, &deployment,
+ cancellable, error))
+ goto out;
+
+ ot_deployment_set_bootconfig (deployment, config);
+ g_ptr_array_add (inout_deployments, g_object_ref (deployment));
+
ret = TRUE;
out:
return ret;
}
+static gint
+compare_deployments_by_boot_loader_version (gconstpointer a_pp,
+ gconstpointer b_pp)
+{
+ OtDeployment *a = *((OtDeployment**)a_pp);
+ OtDeployment *b = *((OtDeployment**)b_pp);
+ OtConfigParser *a_bootconfig = ot_deployment_get_bootconfig (a);
+ OtConfigParser *b_bootconfig = ot_deployment_get_bootconfig (b);
+ const char *a_version = ot_config_parser_get (a_bootconfig, "version");
+ const char *b_version = ot_config_parser_get (b_bootconfig, "version");
+
+ if (a_version && b_version)
+ return strverscmp (a_version, b_version);
+ else if (a_version)
+ return 1;
+ else
+ return -1;
+}
+
gboolean
-ot_admin_list_deployments (GFile *ostree_dir,
- const char *osname,
+ot_admin_list_deployments (GFile *sysroot,
+ int *out_current_bootversion,
GPtrArray **out_deployments,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
- ot_lobj GFileEnumerator *dir_enum = NULL;
- ot_lobj GFileInfo *file_info = NULL;
- ot_lobj GFile *osdir = NULL;
- ot_lptrarray GPtrArray *ret_deployments = NULL;
+ gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL;
+ gs_unref_ptrarray GPtrArray *ret_deployments = NULL;
+ guint i;
+ int bootversion;
- osdir = ot_gfile_get_child_build_path (ostree_dir, "deploy", osname, NULL);
- ret_deployments = g_ptr_array_new_with_free_func (g_object_unref);
+ if (!read_current_bootversion (sysroot, &bootversion, cancellable, error))
+ goto out;
- if (!list_deployments_internal (osdir, ret_deployments, cancellable, error))
+ if (!ot_admin_read_boot_loader_configs (sysroot, bootversion, &boot_loader_configs,
+ cancellable, error))
goto out;
-
+
+ ret_deployments = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
+
+ for (i = 0; i < boot_loader_configs->len; i++)
+ {
+ OtConfigParser *config = boot_loader_configs->pdata[i];
+
+ if (!list_deployments_process_one_boot_entry (sysroot, config, ret_deployments,
+ cancellable, error))
+ goto out;
+ }
+
+ g_ptr_array_sort (ret_deployments, compare_deployments_by_boot_loader_version);
+ for (i = 0; i < ret_deployments->len; i++)
+ {
+ OtDeployment *deployment = ret_deployments->pdata[i];
+ ot_deployment_set_index (deployment, i);
+ }
+
ret = TRUE;
+ *out_current_bootversion = bootversion;
ot_transfer_out_value (out_deployments, &ret_deployments);
out:
return ret;
}
gboolean
-ot_admin_get_booted_os (char **out_osname,
- char **out_tree,
- GCancellable *cancellable,
- GError **error)
+ot_admin_pull (GFile *sysroot,
+ const char *remote,
+ const char *ref,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gs_unref_object GFile *repo_path = g_file_resolve_relative_path (sysroot, "ostree/repo");
+ gs_free char *repo_arg = g_strconcat ("--repo=",
+ gs_file_get_path_cached (repo_path),
+ NULL);
+
+ return gs_subprocess_simple_run_sync (NULL,
+ GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
+ cancellable, error,
+ "ostree", repo_arg, "pull", remote, ref, NULL);
+}
+
+GFile *
+ot_admin_get_deployment_directory (GFile *sysroot,
+ OtDeployment *deployment)
+{
+ gs_free char *path = g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d",
+ ot_deployment_get_osname (deployment),
+ ot_deployment_get_csum (deployment),
+ ot_deployment_get_deployserial (deployment));
+ return g_file_resolve_relative_path (sysroot, path);
+}
+
+static gboolean
+cleanup_other_bootversions (GFile *sysroot,
+ int bootversion,
+ int subbootversion,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- gs_free char *ret_osname = NULL;
- gs_free char *ret_tree = NULL;
- gs_free char *cmdline_contents = NULL;
- const char *iter;
- gsize len;
+ int cleanup_bootversion;
+ int cleanup_subbootversion;
+ gs_free char *cleanup_boot_name = NULL;
+ gs_unref_object GFile *cleanup_boot_dir = NULL;
+
+ cleanup_bootversion = bootversion == 0 ? 1 : 0;
+ cleanup_subbootversion = subbootversion == 0 ? 1 : 0;
+
+ cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "boot/loader.%d", cleanup_bootversion);
+ if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+ goto out;
+ g_clear_object (&cleanup_boot_dir);
+
+ cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d", cleanup_bootversion);
+ if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+ goto out;
+ g_clear_object (&cleanup_boot_dir);
+
+ cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.0", cleanup_bootversion);
+ if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+ goto out;
+ g_clear_object (&cleanup_boot_dir);
+
+ cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.1", cleanup_bootversion);
+ if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+ goto out;
+ g_clear_object (&cleanup_boot_dir);
+
+ cleanup_boot_dir = ot_gfile_resolve_path_printf (sysroot, "ostree/boot.%d.%d", bootversion,
+ cleanup_subbootversion);
+ if (!gs_shutil_rm_rf (cleanup_boot_dir, cancellable, error))
+ goto out;
+ g_clear_object (&cleanup_boot_dir);
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static gboolean
+cleanup_old_deployments (GFile *sysroot,
+ GPtrArray *deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ guint32 root_device;
+ guint64 root_inode;
+ guint i;
+ gs_unref_object GFile *active_root = g_file_new_for_path ("/");
+ gs_unref_hashtable GHashTable *active_deployment_dirs = NULL;
+ gs_unref_ptrarray GPtrArray *all_deployment_dirs = NULL;
- if (!g_file_get_contents ("/proc/cmdline", &cmdline_contents, &len,
- error))
+ if (!get_devino (active_root, &root_device, &root_inode,
+ cancellable, error))
goto out;
- iter = cmdline_contents;
- do
+ active_deployment_dirs = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, NULL, g_object_unref);
+
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+ GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+ /* Transfer ownership */
+ g_hash_table_insert (active_deployment_dirs, deployment_path, deployment_path);
+ }
+
+ if (!list_all_deployment_directories (sysroot, &all_deployment_dirs,
+ cancellable, error))
+ goto out;
+
+ for (i = 0; i < all_deployment_dirs->len; i++)
{
- const char *next = strchr (iter, ' ');
- if (next)
- next += 1;
- if (g_str_has_prefix (iter, "ostree="))
+ OtDeployment *deployment = all_deployment_dirs->pdata[i];
+ gs_unref_object GFile *deployment_path = ot_admin_get_deployment_directory (sysroot, deployment);
+ gs_unref_object GFile *origin_path = ot_admin_get_deployment_origin_path (deployment_path);
+ if (!g_hash_table_lookup (active_deployment_dirs, deployment_path))
{
- const char *slash = strchr (iter, '/');
- if (slash)
- {
- const char *start = iter + strlen ("ostree=");
- ret_osname = g_strndup (start, slash - start);
- if (next)
- ret_tree = g_strndup (slash + 1, next - slash - 1);
- else
- ret_tree = g_strdup (slash + 1);
- break;
- }
+ guint32 device;
+ guint64 inode;
+
+ if (!get_devino (deployment_path, &device, &inode,
+ cancellable, error))
+ goto out;
+
+ /* This shouldn't happen, because higher levels should
+ * disallow having the booted deployment not in the active
+ * deployment list, but let's be extra safe. */
+ if (device == root_device && inode == root_inode)
+ continue;
+
+ g_print ("ostadmin: Deleting deployment %s\n", gs_file_get_path_cached (deployment_path));
+ if (!gs_shutil_rm_rf (deployment_path, cancellable, error))
+ goto out;
+ if (!gs_shutil_rm_rf (origin_path, cancellable, error))
+ goto out;
}
- iter = next;
}
- while (iter != NULL);
ret = TRUE;
out:
- ot_transfer_out_value (out_osname, &ret_osname);
- ot_transfer_out_value (out_tree, &ret_tree);
return ret;
}
-gboolean
-ot_admin_get_active_deployment (GFile *ostree_dir,
- char **out_osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error)
+static gboolean
+cleanup_ref_prefix (OstreeRepo *repo,
+ int bootversion,
+ int subbootversion,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- ot_lptrarray GPtrArray *osnames = NULL;
- ot_lptrarray GPtrArray *deployments = NULL;
- gs_free char *ret_osname = NULL;
- gs_unref_object GFile *ret_deployment = NULL;
+ gs_free char *prefix = NULL;
+ gs_unref_hashtable GHashTable *refs = NULL;
+ GHashTableIter hashiter;
+ gpointer hashkey, hashvalue;
+
+ prefix = g_strdup_printf ("ostree/%d/%d", bootversion, subbootversion);
- if (!ot_admin_get_booted_os (&ret_osname, NULL, cancellable, error))
+ if (!ostree_repo_list_refs (repo, prefix, &refs, cancellable, error))
goto out;
- if (ret_osname != NULL && out_deployment != NULL)
+ g_hash_table_iter_init (&hashiter, refs);
+ while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
{
- gs_unref_object GFile *rootfs_path = NULL;
- gs_unref_object GFileInfo *rootfs_info = NULL;
- guint32 root_dev;
- guint64 root_inode;
- guint i;
-
- rootfs_path = g_file_new_for_path ("/");
- rootfs_info = g_file_query_info (rootfs_path, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
- if (!rootfs_info)
+ const char *suffix = hashkey;
+ gs_free char *ref = g_strconcat (prefix, "/", suffix, NULL);
+ if (!ostree_repo_write_refspec (repo, ref, NULL, error))
goto out;
+ }
- root_dev = g_file_info_get_attribute_uint32 (rootfs_info, "unix::device");
- root_inode = g_file_info_get_attribute_uint64 (rootfs_info, "unix::inode");
+ ret = TRUE;
+ out:
+ return ret;
+}
- if (!ot_admin_list_deployments (ostree_dir, ret_osname, &deployments,
- cancellable, error))
- goto out;
-
- for (i = 0; i < deployments->len; i++)
- {
- GFile *deployment = deployments->pdata[i];
- gs_unref_object GFileInfo *deployment_info = NULL;
- guint32 deploy_dev;
- guint64 deploy_inode;
-
- deployment_info = g_file_query_info (deployment, OSTREE_GIO_FAST_QUERYINFO,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- cancellable, error);
- if (!deployment_info)
- goto out;
+static gboolean
+generate_deployment_refs_and_prune (GFile *sysroot,
+ OstreeRepo *repo,
+ int bootversion,
+ int subbootversion,
+ GPtrArray *deployments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ int cleanup_bootversion;
+ int cleanup_subbootversion;
+ guint i;
+ gint n_objects_total, n_objects_pruned;
+ guint64 freed_space;
+ gs_free char *cleanup_boot_name = NULL;
+ gs_unref_object GFile *cleanup_boot_dir = NULL;
- deploy_dev = g_file_info_get_attribute_uint32 (deployment_info, "unix::device");
- deploy_inode = g_file_info_get_attribute_uint64 (deployment_info, "unix::inode");
+ cleanup_bootversion = (bootversion == 0) ? 1 : 0;
+ cleanup_subbootversion = (subbootversion == 0) ? 1 : 0;
- if (root_dev == deploy_dev && root_inode == deploy_inode)
- {
- ret_deployment = g_object_ref (deployment);
- break;
- }
- }
-
- g_assert (ret_deployment != NULL);
+ if (!cleanup_ref_prefix (repo, cleanup_bootversion, 0,
+ cancellable, error))
+ goto out;
+
+ if (!cleanup_ref_prefix (repo, cleanup_bootversion, 1,
+ cancellable, error))
+ goto out;
+
+ if (!cleanup_ref_prefix (repo, bootversion, cleanup_subbootversion,
+ cancellable, error))
+ goto out;
+
+ for (i = 0; i < deployments->len; i++)
+ {
+ OtDeployment *deployment = deployments->pdata[i];
+ gs_free char *refname = g_strdup_printf ("ostree/%d/%d/%u",
+ bootversion, subbootversion,
+ i);
+ if (!ostree_repo_write_refspec (repo, refname, ot_deployment_get_csum (deployment),
+ error))
+ goto out;
+ }
+
+ if (!ostree_prune (repo, OSTREE_PRUNE_FLAGS_REFS_ONLY, 0,
+ &n_objects_total, &n_objects_pruned, &freed_space,
+ cancellable, error))
+ goto out;
+ if (freed_space > 0)
+ {
+ char *freed_space_str = g_format_size_full (freed_space, 0);
+ g_print ("Freed objects: %s\n", freed_space_str);
}
ret = TRUE;
- ot_transfer_out_value (out_osname, &ret_osname);
- ot_transfer_out_value (out_deployment, &ret_deployment);
out:
return ret;
}
-
+
gboolean
-ot_admin_get_default_ostree_dir (GFile **out_ostree_dir,
- GCancellable *cancellable,
- GError **error)
+ot_admin_cleanup (GFile *sysroot,
+ GCancellable *cancellable,
+ GError **error)
{
gboolean ret = FALSE;
- gs_unref_object GFile *possible_ostree_dir = NULL;
- gs_unref_object GFile *ret_ostree_dir = NULL;
- gs_unref_object GFile *host_usr = NULL;
+ gs_unref_ptrarray GPtrArray *deployments = NULL;
+ gs_unref_object OstreeRepo *repo = NULL;
+ int bootversion;
+ int subbootversion;
- host_usr = g_file_new_for_path ("/usr");
+ if (!ot_admin_list_deployments (sysroot, &bootversion, &deployments,
+ cancellable, error))
+ goto out;
- if (ret_ostree_dir == NULL)
- {
- g_clear_object (&possible_ostree_dir);
- possible_ostree_dir = g_file_new_for_path ("/sysroot/ostree");
- if (g_file_query_exists (possible_ostree_dir, NULL))
- ret_ostree_dir = g_object_ref (possible_ostree_dir);
- }
- if (ret_ostree_dir == NULL)
+ if (!ot_admin_read_current_subbootversion (sysroot, bootversion, &subbootversion,
+ cancellable, error))
+ goto out;
+
+ if (!cleanup_other_bootversions (sysroot, bootversion, subbootversion,
+ cancellable, error))
+ goto out;
+
+ if (!cleanup_old_deployments (sysroot, deployments,
+ cancellable, error))
+ goto out;
+
+ if (deployments->len > 0)
{
- g_clear_object (&possible_ostree_dir);
- possible_ostree_dir = g_file_new_for_path ("/ostree");
- /* If there's also /usr, we assume we're outside an ostree root
- * and thus should use /ostree.
- */
- if (g_file_query_exists (possible_ostree_dir, NULL) ||
- g_file_query_exists (host_usr, NULL))
- ret_ostree_dir = g_object_ref (possible_ostree_dir);
+ if (!ot_admin_get_repo (sysroot, &repo, cancellable, error))
+ goto out;
+
+ if (!generate_deployment_refs_and_prune (sysroot, repo, bootversion,
+ subbootversion, deployments,
+ cancellable, error))
+ goto out;
}
ret = TRUE;
- ot_transfer_out_value (out_ostree_dir, &ret_ostree_dir);
+ out:
return ret;
}
-gboolean
-ot_admin_pull (GFile *ostree_dir,
- const char *osname,
- GCancellable *cancellable,
- GError **error)
+OtBootloader *
+ot_admin_query_bootloader (GFile *sysroot)
{
- gs_unref_object GFile *repo_path = g_file_get_child (ostree_dir, "repo");
- gs_free char *repo_arg = g_strconcat ("--repo=",
- gs_file_get_path_cached (repo_path),
- NULL);
+ OtBootloaderSyslinux *syslinux;
- return gs_subprocess_simple_run_sync (gs_file_get_path_cached (ostree_dir),
- GS_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
- cancellable, error,
- "ostree", repo_arg, "pull", osname, NULL);
+ syslinux = ot_bootloader_syslinux_new (sysroot);
+ if (ot_bootloader_query ((OtBootloader*)syslinux))
+ return (OtBootloader*) (syslinux);
+
+ return NULL;
}
-void
-ot_admin_parse_deploy_name (GFile *ostree_dir,
- const char *osname,
- GFile *deployment,
- char **out_name,
- char **out_rev)
+GKeyFile *
+ot_origin_new_from_refspec (const char *refspec)
{
- gs_unref_object GFile *deploy_dir = g_file_get_child (ostree_dir, "deploy");
- gs_unref_object GFile *os_dir = g_file_get_child (deploy_dir, osname);
- gs_free char *relpath = g_file_get_relative_path (os_dir, deployment);
- const char *last_dash;
+ GKeyFile *ret = g_key_file_new ();
+ g_key_file_set_string (ret, "origin", "refspec", refspec);
+ return ret;
+}
- g_assert (relpath);
- last_dash = strrchr (relpath, '-');
- if (!last_dash)
- g_error ("Failed to parse deployment name %s", relpath);
-
- if (out_name)
- *out_name = g_strndup (relpath, last_dash - relpath);
- if (out_rev)
- *out_rev = g_strdup (last_dash + 1);
+gboolean
+ot_admin_get_repo (GFile *sysroot,
+ OstreeRepo **out_repo,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object OstreeRepo *ret_repo = NULL;
+ gs_unref_object GFile *repo_path = g_file_resolve_relative_path (sysroot, "ostree/repo");
+
+ ret_repo = ostree_repo_new (repo_path);
+ if (!ostree_repo_check (ret_repo, error))
+ goto out;
+
+ ret = TRUE;
+ ot_transfer_out_value (out_repo, &ret_repo);
+ out:
+ return ret;
}
#define __OT_ADMIN_FUNCTIONS__
#include <gio/gio.h>
+#include "ostree.h"
+#include "ot-deployment.h"
+#include "ot-bootloader.h"
+#include "ot-ordered-hash.h"
G_BEGIN_DECLS
GCancellable *cancellable,
GError **error);
-gboolean ot_admin_get_booted_os (char **out_osname,
- char **out_tree,
- GCancellable *cancellable,
- GError **error);
-
-gboolean ot_admin_get_current_deployment (GFile *ostree_dir,
- const char *osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error);
-gboolean ot_admin_get_previous_deployment (GFile *ostree_dir,
- const char *osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error);
-
-gboolean ot_admin_list_deployments (GFile *ostree_dir,
- const char *osname,
+gboolean ot_admin_check_os (GFile *sysroot,
+ const char *osname,
+ GCancellable *cancellable,
+ GError **error);
+
+char *ot_admin_split_keyeq (char *str);
+OtOrderedHash *ot_admin_parse_kernel_args (const char *options);
+char * ot_admin_kernel_arg_string_serialize (OtOrderedHash *ohash);
+
+OtBootloader *ot_admin_query_bootloader (GFile *sysroot);
+
+gboolean ot_admin_read_current_subbootversion (GFile *sysroot,
+ int bootversion,
+ int *out_subbootversion,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_admin_read_boot_loader_configs (GFile *boot_dir,
+ int bootversion,
+ GPtrArray **out_loader_configs,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_admin_list_deployments (GFile *sysroot,
+ int *out_bootversion,
GPtrArray **out_deployments,
GCancellable *cancellable,
GError **error);
-gboolean ot_admin_get_active_deployment (GFile *ostree_dir,
- char **out_osname,
- GFile **out_deployment,
- GCancellable *cancellable,
- GError **error);
+gboolean ot_admin_find_booted_deployment (GFile *sysroot,
+ GPtrArray *deployments,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_admin_require_booted_deployment (GFile *sysroot,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_admin_require_deployment_or_osname (GFile *sysroot,
+ GPtrArray *deployment_list,
+ const char *osname,
+ OtDeployment **out_deployment,
+ GCancellable *cancellable,
+ GError **error);
+
+OtDeployment *ot_admin_get_merge_deployment (GPtrArray *deployment_list,
+ const char *osname,
+ OtDeployment *booted_deployment,
+ OtDeployment *new_deployment);
+
+GFile *ot_admin_get_deployment_origin_path (GFile *deployment_path);
+
+GFile *ot_admin_get_deployment_directory (GFile *sysroot,
+ OtDeployment *deployment);
+
+gboolean ot_admin_get_repo (GFile *sysroot,
+ OstreeRepo **out_repo,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_admin_cleanup (GFile *sysroot,
+ GCancellable *cancellable,
+ GError **error);
gboolean ot_admin_get_default_ostree_dir (GFile **out_ostree_dir,
GCancellable *cancellable,
GError **error);
+GKeyFile *ot_origin_new_from_refspec (const char *refspec);
+
gboolean ot_admin_pull (GFile *ostree_dir,
- const char *osname,
+ const char *remote,
+ const char *ref,
GCancellable *cancellable,
GError **error);
-void
-ot_admin_parse_deploy_name (GFile *ostree_dir,
- const char *osname,
- GFile *deployment,
- char **out_name,
- char **out_rev);
-
G_END_DECLS
#endif
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-bootloader-syslinux.h"
+#include "libgsystem.h"
+#include "otutil.h"
+#include "ot-admin-functions.h"
+
+#include <string.h>
+
+struct _OtBootloaderSyslinux
+{
+ GObject parent_instance;
+
+ GFile *sysroot;
+ GFile *config_path;
+};
+
+typedef GObjectClass OtBootloaderSyslinuxClass;
+
+static void ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (OtBootloaderSyslinux, ot_bootloader_syslinux, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (OT_TYPE_BOOTLOADER, ot_bootloader_syslinux_bootloader_iface_init));
+
+static gboolean
+ot_bootloader_syslinux_query (OtBootloader *bootloader)
+{
+ OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
+
+ return g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK;
+}
+
+static gboolean
+append_config_from_boot_loader_entries (OtBootloaderSyslinux *self,
+ gboolean regenerate_default,
+ int bootversion,
+ GPtrArray *new_lines,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_ptrarray GPtrArray *boot_loader_configs = NULL;
+ guint i;
+
+ if (!ot_admin_read_boot_loader_configs (self->sysroot, bootversion, &boot_loader_configs,
+ cancellable, error))
+ goto out;
+
+ for (i = 0; i < boot_loader_configs->len; i++)
+ {
+ OtConfigParser *config = boot_loader_configs->pdata[i];
+ const char *val;
+
+ val = ot_config_parser_get (config, "title");
+ if (!val)
+ val = "(Untitled)";
+
+ if (regenerate_default && i == 0)
+ {
+ g_ptr_array_add (new_lines, g_strdup_printf ("DEFAULT %s", val));
+ }
+
+ g_ptr_array_add (new_lines, g_strdup_printf ("LABEL %s", val));
+
+ val = ot_config_parser_get (config, "linux");
+ if (!val)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No \"linux\" key in bootloader config");
+ goto out;
+ }
+ g_ptr_array_add (new_lines, g_strdup_printf ("\tKERNEL %s", val));
+
+ val = ot_config_parser_get (config, "initrd");
+ if (val)
+ g_ptr_array_add (new_lines, g_strdup_printf ("\tINITRD %s", val));
+
+ val = ot_config_parser_get (config, "options");
+ if (val)
+ g_ptr_array_add (new_lines, g_strdup_printf ("\tAPPEND %s", val));
+ }
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static char *
+join_lines (GPtrArray *lines)
+{
+ GString *buf = g_string_new ("");
+ guint i;
+ gboolean prev_was_empty = FALSE;
+
+ for (i = 0; i < lines->len; i++)
+ {
+ const char *line = lines->pdata[i];
+ /* Special bit to remove extraneous empty lines */
+ if (*line == '\0')
+ {
+ if (prev_was_empty || i == 0)
+ continue;
+ else
+ prev_was_empty = TRUE;
+ }
+ g_string_append (buf, line);
+ g_string_append_c (buf, '\n');
+ }
+ return g_string_free (buf, FALSE);
+}
+
+static gboolean
+ot_bootloader_syslinux_write_config (OtBootloader *bootloader,
+ int bootversion,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (bootloader);
+ gs_unref_object GFile *new_config_path = NULL;
+ gs_free char *config_contents = NULL;
+ gs_free char *new_config_contents = NULL;
+ gs_unref_ptrarray GPtrArray *new_lines = NULL;
+ gs_unref_ptrarray GPtrArray *tmp_lines = NULL;
+ gs_free char *kernel_arg = NULL;
+ gboolean saw_default = FALSE;
+ gboolean regenerate_default = FALSE;
+ gboolean parsing_label = FALSE;
+ char **lines = NULL;
+ char **iter;
+ guint i;
+
+ new_config_path = ot_gfile_resolve_path_printf (self->sysroot, "boot/loader.%d/syslinux.cfg",
+ bootversion);
+
+ /* This should follow the symbolic link to the current bootversion. */
+ config_contents = gs_file_load_contents_utf8 (self->config_path, cancellable, error);
+ if (!config_contents)
+ goto out;
+
+ lines = g_strsplit (config_contents, "\n", -1);
+ new_lines = g_ptr_array_new_with_free_func (g_free);
+ tmp_lines = g_ptr_array_new_with_free_func (g_free);
+
+ /* Note special iteration condition here; we want to also loop one
+ * more time at the end where line = NULL to ensure we finish off
+ * processing the last LABEL.
+ */
+ iter = lines;
+ while (TRUE)
+ {
+ char *line = *iter;
+ gboolean skip = FALSE;
+
+ if (parsing_label &&
+ (line == NULL || !g_str_has_prefix (line, "\t")))
+ {
+ parsing_label = FALSE;
+ if (kernel_arg == NULL)
+ {
+ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No KERNEL argument found after LABEL");
+ goto out;
+ }
+
+ /* If this is a non-ostree kernel, just emit the lines
+ * we saw.
+ */
+ if (!g_str_has_prefix (kernel_arg, "/ostree/"))
+ {
+ for (i = 0; i < tmp_lines->len; i++)
+ {
+ g_ptr_array_add (new_lines, tmp_lines->pdata[i]);
+ tmp_lines->pdata[i] = NULL; /* Transfer ownership */
+ }
+ }
+ else
+ {
+ /* Otherwise, we drop the config on the floor - it
+ * will be regenerated.
+ */
+ g_ptr_array_set_size (tmp_lines, 0);
+ }
+ }
+
+ if (line == NULL)
+ break;
+
+ if (!parsing_label &&
+ (g_str_has_prefix (line, "LABEL ")))
+ {
+ parsing_label = TRUE;
+ g_ptr_array_set_size (tmp_lines, 0);
+ }
+ else if (parsing_label && g_str_has_prefix (line, "\tKERNEL "))
+ {
+ g_free (kernel_arg);
+ kernel_arg = g_strdup (line + strlen ("\tKERNEL "));
+ }
+ else if (!parsing_label &&
+ (g_str_has_prefix (line, "DEFAULT ")))
+ {
+ saw_default = TRUE;
+ if (g_str_has_prefix (line, "DEFAULT ostree:"))
+ regenerate_default = TRUE;
+ skip = TRUE;
+ }
+
+ if (skip)
+ {
+ g_free (line);
+ }
+ else
+ {
+ if (parsing_label)
+ {
+ g_ptr_array_add (tmp_lines, line);
+ }
+ else
+ {
+ g_ptr_array_add (new_lines, line);
+ }
+ }
+ /* Transfer ownership */
+ *iter = NULL;
+ iter++;
+ }
+
+ if (!saw_default)
+ regenerate_default = TRUE;
+
+ if (!append_config_from_boot_loader_entries (self, regenerate_default,
+ bootversion, new_lines,
+ cancellable, error))
+ goto out;
+
+ new_config_contents = join_lines (new_lines);
+
+ if (strcmp (new_config_contents, config_contents) != 0)
+ {
+ if (!g_file_replace_contents (new_config_path, new_config_contents,
+ strlen (new_config_contents),
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, cancellable, error))
+ goto out;
+ g_print ("Saved new version of %s\n", gs_file_get_path_cached (self->config_path));
+ }
+
+ ret = TRUE;
+ out:
+ g_free (lines); /* Note we freed elements individually */
+ return ret;
+}
+
+static void
+ot_bootloader_syslinux_finalize (GObject *object)
+{
+ OtBootloaderSyslinux *self = OT_BOOTLOADER_SYSLINUX (object);
+
+ g_clear_object (&self->sysroot);
+ g_clear_object (&self->config_path);
+
+ G_OBJECT_CLASS (ot_bootloader_syslinux_parent_class)->finalize (object);
+}
+
+void
+ot_bootloader_syslinux_init (OtBootloaderSyslinux *self)
+{
+}
+
+static void
+ot_bootloader_syslinux_bootloader_iface_init (OtBootloaderInterface *iface)
+{
+ iface->query = ot_bootloader_syslinux_query;
+ iface->write_config = ot_bootloader_syslinux_write_config;
+}
+
+void
+ot_bootloader_syslinux_class_init (OtBootloaderSyslinuxClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = ot_bootloader_syslinux_finalize;
+}
+
+OtBootloaderSyslinux *
+ot_bootloader_syslinux_new (GFile *sysroot)
+{
+ OtBootloaderSyslinux *self = g_object_new (OT_TYPE_BOOTLOADER_SYSLINUX, NULL);
+ self->sysroot = g_object_ref (sysroot);
+ self->config_path = g_file_resolve_relative_path (self->sysroot, "boot/syslinux/syslinux.cfg");
+ return self;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OT_BOOTLOADER_SYSLINUX_H__
+#define __OT_BOOTLOADER_SYSLINUX_H__
+
+#include "ot-bootloader.h"
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_BOOTLOADER_SYSLINUX (ot_bootloader_syslinux_get_type ())
+#define OT_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER_SYSLINUX, OtBootloaderSyslinux))
+#define OT_IS_BOOTLOADER_SYSLINUX(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER_SYSLINUX))
+
+typedef struct _OtBootloaderSyslinux OtBootloaderSyslinux;
+
+GType ot_bootloader_syslinux_get_type (void) G_GNUC_CONST;
+
+OtBootloaderSyslinux * ot_bootloader_syslinux_new (GFile *sysroot);
+
+G_END_DECLS
+
+#endif /* __OT_BOOTLOADER_SYSLINUX_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#include "ot-bootloader.h"
+
+G_DEFINE_INTERFACE (OtBootloader, ot_bootloader, G_TYPE_OBJECT)
+
+static void
+ot_bootloader_default_init (OtBootloaderInterface *iface)
+{
+}
+
+gboolean
+ot_bootloader_query (OtBootloader *self)
+{
+ g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
+
+ return OT_BOOTLOADER_GET_IFACE (self)->query (self);
+}
+
+gboolean
+ot_bootloader_write_config (OtBootloader *self,
+ int bootversion,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (OT_IS_BOOTLOADER (self), FALSE);
+
+ return OT_BOOTLOADER_GET_IFACE (self)->write_config (self, bootversion,
+ cancellable, error);
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OT_BOOTLOADER_H__
+#define __OT_BOOTLOADER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_BOOTLOADER (ot_bootloader_get_type ())
+#define OT_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_BOOTLOADER, OtBootloader))
+#define OT_IS_BOOTLOADER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_BOOTLOADER))
+#define OT_BOOTLOADER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), OT_TYPE_BOOTLOADER, OtBootloaderInterface))
+
+typedef struct _OtBootloader OtBootloader;
+typedef struct _OtBootloaderInterface OtBootloaderInterface;
+
+struct _OtBootloaderInterface
+{
+ GTypeInterface g_iface;
+
+ /* virtual functions */
+ gboolean (* query) (OtBootloader *self);
+ gboolean (* write_config) (OtBootloader *self,
+ int bootversion,
+ GCancellable *cancellable,
+ GError **error);
+};
+
+GType ot_bootloader_get_type (void) G_GNUC_CONST;
+
+gboolean ot_bootloader_query (OtBootloader *self);
+
+gboolean ot_bootloader_write_config (OtBootloader *self,
+ int bootversion,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __OT_BOOTLOADER_H__ */
#include <glib/gi18n.h>
-static char *opt_ostree_dir = NULL;
-static char *opt_boot_dir = "/boot";
+static char *opt_sysroot = "/";
static GOptionEntry options[] = {
- { "ostree-dir", 0, 0, G_OPTION_ARG_STRING, &opt_ostree_dir, "Path to OSTree root directory (default: /ostree)", NULL },
- { "boot-dir", 0, 0, G_OPTION_ARG_STRING, &opt_boot_dir, "Path to system boot directory (default: /boot)", NULL },
+ { "sysroot", 0, 0, G_OPTION_ARG_STRING, &opt_sysroot, "Path to root directory (default: /)", NULL },
{ NULL }
};
{ "deploy", ot_admin_builtin_deploy },
{ "install", ot_admin_builtin_install },
{ "upgrade", ot_admin_builtin_upgrade },
- { "pull-deploy", ot_admin_builtin_pull_deploy },
{ "prune", ot_admin_builtin_prune },
- { "update-kernel", ot_admin_builtin_update_kernel },
+ { "status", ot_admin_builtin_status },
{ "config-diff", ot_admin_builtin_diff },
{ "run-triggers", ot_admin_builtin_run_triggers },
{ NULL, NULL }
int subcmd_argc;
OtAdminBuiltinOpts admin_opts;
char **subcmd_argv = NULL;
- ot_lobj GFile *ostree_dir = NULL;
- ot_lobj GFile *boot_dir = NULL;
context = g_option_context_new ("[OPTIONS] SUBCOMMAND - Run an administrative subcommand");
goto out;
}
- if (opt_ostree_dir != NULL)
- {
- ostree_dir = g_file_new_for_path (opt_ostree_dir);
- }
- else
- {
- if (!ot_admin_get_default_ostree_dir (&ostree_dir, cancellable, error))
- goto out;
- }
- boot_dir = g_file_new_for_path (opt_boot_dir);
-
ostree_prep_builtin_argv (subcommand_name, argc-2, argv+2, &subcmd_argc, &subcmd_argv);
- admin_opts.ostree_dir = ostree_dir;
- admin_opts.boot_dir = boot_dir;
+ admin_opts.sysroot = g_file_new_for_path (opt_sysroot);
if (!subcommand->fn (subcmd_argc, subcmd_argv, &admin_opts, error))
goto out;
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-config-parser.h"
+#include "libgsystem.h"
+
+struct _OtConfigParser
+{
+ GObject parent_instance;
+
+ gboolean parsed;
+ char *separators;
+
+ GHashTable *options;
+ GPtrArray *lines;
+};
+
+typedef GObjectClass OtConfigParserClass;
+
+G_DEFINE_TYPE (OtConfigParser, ot_config_parser, G_TYPE_OBJECT)
+
+gboolean
+ot_config_parser_parse (OtConfigParser *self,
+ GFile *path,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_free char *contents = NULL;
+ char **lines = NULL;
+ char **iter = NULL;
+
+ g_return_val_if_fail (!self->parsed, FALSE);
+
+ contents = gs_file_load_contents_utf8 (path, cancellable, error);
+ if (!contents)
+ goto out;
+
+ lines = g_strsplit (contents, "\n", -1);
+ for (iter = lines; *iter; iter++)
+ {
+ const char *line = *iter;
+ char *keyname = "";
+
+ if (g_ascii_isalpha (*line))
+ {
+ char **items = NULL;
+ items = g_strsplit_set (line, self->separators, 2);
+ if (g_strv_length (items) == 2 && items[0][0] != '\0')
+ {
+ keyname = items[0];
+ g_hash_table_insert (self->options, items[0], items[1]);
+ g_free (items); /* Transfer ownership */
+ }
+ else
+ {
+ g_strfreev (items);
+ }
+ }
+ g_ptr_array_add (self->lines, g_variant_new ("(ss)", keyname, line));
+ }
+
+ self->parsed = TRUE;
+
+ ret = TRUE;
+ out:
+ g_strfreev (lines);
+ return ret;
+}
+
+void
+ot_config_parser_set (OtConfigParser *self,
+ const char *key,
+ const char *value)
+{
+ g_hash_table_replace (self->options, g_strdup (key), g_strdup (value));
+}
+
+const char *
+ot_config_parser_get (OtConfigParser *self,
+ const char *key)
+{
+ return g_hash_table_lookup (self->options, key);
+}
+
+static gboolean
+write_key (OtConfigParser *self,
+ GDataOutputStream *out,
+ const char *key,
+ const char *value,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+
+ if (!g_data_output_stream_put_string (out, key, cancellable, error))
+ goto out;
+ if (!g_data_output_stream_put_byte (out, self->separators[0], cancellable, error))
+ goto out;
+ if (!g_data_output_stream_put_string (out, value, cancellable, error))
+ goto out;
+ if (!g_data_output_stream_put_byte (out, '\n', cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+gboolean
+ot_config_parser_write (OtConfigParser *self,
+ GFile *output,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ GHashTableIter hashiter;
+ gpointer hashkey, hashvalue;
+ gs_unref_object GOutputStream *out = NULL;
+ gs_unref_object GDataOutputStream *dataout = NULL;
+ guint i;
+ gs_unref_hashtable GHashTable *written_overrides = NULL;
+
+ written_overrides = g_hash_table_new (g_str_hash, g_str_equal);
+
+ out = (GOutputStream*)g_file_replace (output, NULL, FALSE, 0, cancellable, error);
+ if (!out)
+ goto out;
+
+ dataout = g_data_output_stream_new (out);
+
+ for (i = 0; i < self->lines->len; i++)
+ {
+ GVariant *linedata = self->lines->pdata[i];
+ const char *key;
+ const char *value;
+ const char *line;
+
+ g_variant_get (linedata, "(&s&s)", &key, &line);
+
+ value = g_hash_table_lookup (self->options, key);
+ if (value == NULL)
+ {
+ if (!g_data_output_stream_put_string (dataout, line, cancellable, error))
+ goto out;
+ if (!g_data_output_stream_put_byte (dataout, '\n', cancellable, error))
+ goto out;
+ }
+ else
+ {
+ if (!write_key (self, dataout, key, value, cancellable, error))
+ goto out;
+ g_hash_table_insert (written_overrides, (gpointer)key, (gpointer)key);
+ }
+ }
+
+ g_hash_table_iter_init (&hashiter, self->options);
+ while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue))
+ {
+ if (g_hash_table_lookup (written_overrides, hashkey))
+ continue;
+ if (!write_key (self, dataout, hashkey, hashvalue, cancellable, error))
+ goto out;
+ }
+
+ if (!g_output_stream_close ((GOutputStream*)dataout, cancellable, error))
+ goto out;
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+static void
+ot_config_parser_finalize (GObject *object)
+{
+ OtConfigParser *self = OT_CONFIG_PARSER (object);
+
+ g_hash_table_unref (self->options);
+ g_ptr_array_unref (self->lines);
+ g_free (self->separators);
+
+ G_OBJECT_CLASS (ot_config_parser_parent_class)->finalize (object);
+}
+
+static void
+ot_config_parser_init (OtConfigParser *self)
+{
+ self->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ self->lines = g_ptr_array_new ();
+}
+
+void
+ot_config_parser_class_init (OtConfigParserClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = ot_config_parser_finalize;
+}
+
+OtConfigParser *
+ot_config_parser_new (const char *separators)
+{
+ OtConfigParser *self = NULL;
+
+ g_return_val_if_fail (separators != NULL && separators[0], NULL);
+
+ self = g_object_new (OT_TYPE_CONFIG_PARSER, NULL);
+ self->separators = g_strdup (separators);
+ return self;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OT_CONFIG_PARSER_H__
+#define __OT_CONFIG_PARSER_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_CONFIG_PARSER (ot_config_parser_get_type ())
+#define OT_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_CONFIG_PARSER, OtConfigParser))
+#define OT_IS_CONFIG_PARSER(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_CONFIG_PARSER))
+
+typedef struct _OtConfigParser OtConfigParser;
+
+GType ot_config_parser_get_type (void) G_GNUC_CONST;
+
+OtConfigParser * ot_config_parser_new (const char *separator);
+
+gboolean ot_config_parser_parse (OtConfigParser *self,
+ GFile *path,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean ot_config_parser_write (OtConfigParser *self,
+ GFile *output,
+ GCancellable *cancellable,
+ GError **error);
+
+void ot_config_parser_set (OtConfigParser *self,
+ const char *key,
+ const char *value);
+
+const char *ot_config_parser_get (OtConfigParser *self,
+ const char *key);
+
+
+G_END_DECLS
+
+#endif /* __OT_CONFIG_PARSER_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-deployment.h"
+
+struct _OtDeployment
+{
+ GObject parent_instance;
+
+ int index; /* Global offset */
+ char *osname; /* osname */
+ char *csum; /* OSTree checksum of tree */
+ int deployserial; /* How many times this particular csum appears in deployment list */
+ char *bootcsum; /* Checksum of kernel+initramfs */
+ int bootserial; /* An integer assigned to this tree per its ${bootcsum} */
+ OtConfigParser *bootconfig; /* Bootloader configuration */
+ GKeyFile *origin; /* How to construct an upgraded version of this tree */
+};
+
+typedef GObjectClass OtDeploymentClass;
+
+G_DEFINE_TYPE (OtDeployment, ot_deployment, G_TYPE_OBJECT)
+
+const char *
+ot_deployment_get_csum (OtDeployment *self)
+{
+ return self->csum;
+}
+
+const char *
+ot_deployment_get_bootcsum (OtDeployment *self)
+{
+ return self->bootcsum;
+}
+
+const char *
+ot_deployment_get_osname (OtDeployment *self)
+{
+ return self->osname;
+}
+
+int
+ot_deployment_get_deployserial (OtDeployment *self)
+{
+ return self->deployserial;
+}
+
+int
+ot_deployment_get_bootserial (OtDeployment *self)
+{
+ return self->bootserial;
+}
+
+OtConfigParser *
+ot_deployment_get_bootconfig (OtDeployment *self)
+{
+ return self->bootconfig;
+}
+
+GKeyFile *
+ot_deployment_get_origin (OtDeployment *self)
+{
+ return self->origin;
+}
+
+int
+ot_deployment_get_index (OtDeployment *self)
+{
+ return self->index;
+}
+
+void
+ot_deployment_set_index (OtDeployment *self, int index)
+{
+ self->index = index;
+}
+
+void
+ot_deployment_set_bootserial (OtDeployment *self, int index)
+{
+ self->bootserial = index;
+}
+
+void
+ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig)
+{
+ g_clear_object (&self->bootconfig);
+ if (bootconfig)
+ self->bootconfig = g_object_ref (bootconfig);
+}
+
+void
+ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin)
+{
+ g_clear_pointer (&self->origin, g_key_file_unref);
+ if (origin)
+ self->origin = g_key_file_ref (origin);
+}
+
+OtDeployment *
+ot_deployment_clone (OtDeployment *self)
+{
+ OtDeployment *ret = ot_deployment_new (self->index, self->osname, self->csum,
+ self->deployserial,
+ self->bootcsum, self->bootserial);
+ ot_deployment_set_bootconfig (ret, self->bootconfig);
+ ot_deployment_set_origin (ret, self->origin);
+ return ret;
+}
+
+guint
+ot_deployment_hash (gconstpointer v)
+{
+ OtDeployment *d = (OtDeployment*)v;
+ return g_str_hash (ot_deployment_get_osname (d)) +
+ g_str_hash (ot_deployment_get_csum (d)) +
+ ot_deployment_get_deployserial (d);
+}
+
+gboolean
+ot_deployment_equal (gconstpointer ap, gconstpointer bp)
+{
+ OtDeployment *a = (OtDeployment*)ap;
+ OtDeployment *b = (OtDeployment*)bp;
+
+ if (a == NULL && b == NULL)
+ return TRUE;
+ else if (a != NULL && b != NULL)
+ return g_str_equal (ot_deployment_get_osname (a),
+ ot_deployment_get_osname (b)) &&
+ g_str_equal (ot_deployment_get_csum (a),
+ ot_deployment_get_csum (b)) &&
+ ot_deployment_get_deployserial (a) == ot_deployment_get_deployserial (b);
+ else
+ return FALSE;
+}
+
+static void
+ot_deployment_finalize (GObject *object)
+{
+ OtDeployment *self = OT_DEPLOYMENT (object);
+
+ g_free (self->osname);
+ g_free (self->csum);
+ g_free (self->bootcsum);
+ g_clear_object (&self->bootconfig);
+ g_clear_pointer (&self->origin, g_key_file_unref);
+
+ G_OBJECT_CLASS (ot_deployment_parent_class)->finalize (object);
+}
+
+void
+ot_deployment_init (OtDeployment *self)
+{
+}
+
+void
+ot_deployment_class_init (OtDeploymentClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = ot_deployment_finalize;
+}
+
+OtDeployment *
+ot_deployment_new (int index,
+ const char *osname,
+ const char *csum,
+ int deployserial,
+ const char *bootcsum,
+ int bootserial)
+{
+ OtDeployment *self;
+
+ /* index may be -1 */
+ g_return_val_if_fail (osname != NULL, NULL);
+ g_return_val_if_fail (csum != NULL, NULL);
+ g_return_val_if_fail (deployserial >= 0, NULL);
+ /* We can have "disconnected" deployments that don't have a
+ bootcsum/serial */
+
+ self = g_object_new (OT_TYPE_DEPLOYMENT, NULL);
+ self->index = index;
+ self->osname = g_strdup (osname);
+ self->csum = g_strdup (csum);
+ self->deployserial = deployserial;
+ self->bootcsum = g_strdup (bootcsum);
+ self->bootserial = bootserial;
+ return self;
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OT_DEPLOYMENT_H__
+#define __OT_DEPLOYMENT_H__
+
+#include <gio/gio.h>
+#include "ot-config-parser.h"
+
+G_BEGIN_DECLS
+
+#define OT_TYPE_DEPLOYMENT (ot_deployment_get_type ())
+#define OT_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OT_TYPE_DEPLOYMENT, OtDeployment))
+#define OT_IS_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OT_TYPE_DEPLOYMENT))
+
+typedef struct _OtDeployment OtDeployment;
+
+GType ot_deployment_get_type (void) G_GNUC_CONST;
+
+guint ot_deployment_hash (gconstpointer v);
+gboolean ot_deployment_equal (gconstpointer a, gconstpointer b);
+
+OtDeployment * ot_deployment_new (int index,
+ const char *osname,
+ const char *csum,
+ int deployserial,
+ const char *bootcsum,
+ int bootserial);
+
+int ot_deployment_get_index (OtDeployment *self);
+const char *ot_deployment_get_osname (OtDeployment *self);
+int ot_deployment_get_deployserial (OtDeployment *self);
+const char *ot_deployment_get_csum (OtDeployment *self);
+const char *ot_deployment_get_bootcsum (OtDeployment *self);
+int ot_deployment_get_bootserial (OtDeployment *self);
+OtConfigParser *ot_deployment_get_bootconfig (OtDeployment *self);
+GKeyFile *ot_deployment_get_origin (OtDeployment *self);
+
+void ot_deployment_set_index (OtDeployment *self, int index);
+void ot_deployment_set_bootserial (OtDeployment *self, int index);
+void ot_deployment_set_bootconfig (OtDeployment *self, OtConfigParser *bootconfig);
+void ot_deployment_set_origin (OtDeployment *self, GKeyFile *origin);
+
+OtDeployment *ot_deployment_clone (OtDeployment *self);
+
+
+G_END_DECLS
+
+#endif /* __OT_DEPLOYMENT_H__ */
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-ordered-hash.h"
+
+OtOrderedHash *
+ot_ordered_hash_new (void)
+{
+ OtOrderedHash *ret;
+ ret = g_new0 (OtOrderedHash, 1);
+ ret->order = g_ptr_array_new_with_free_func (g_free);
+ ret->table = g_hash_table_new (g_str_hash, g_str_equal);
+ return ret;
+}
+
+void
+ot_ordered_hash_free (OtOrderedHash *ohash)
+{
+ if (!ohash)
+ return;
+ g_ptr_array_unref (ohash->order);
+ g_hash_table_unref (ohash->table);
+ g_free (ohash);
+}
+
+void
+ot_ordered_hash_cleanup (void *loc)
+{
+ ot_ordered_hash_free (*((OtOrderedHash**)loc));
+}
+
+void
+ot_ordered_hash_replace_key_take (OtOrderedHash *ohash,
+ char *key,
+ const char *value)
+{
+ gboolean existed;
+
+ existed = g_hash_table_remove (ohash->table, key);
+ if (!existed)
+ g_ptr_array_add (ohash->order, key);
+ g_hash_table_insert (ohash->table, key, (char*)value);
+}
+
+void
+ot_ordered_hash_replace_key (OtOrderedHash *ohash,
+ const char *key,
+ const char *val)
+{
+ GString *buf;
+ gsize keylen;
+ char *valp;
+ char *valblock;
+
+ buf = g_string_new (key);
+ keylen = buf->len;
+ g_string_append_c (buf, '\0');
+ g_string_append (buf, val);
+ valblock = g_string_free (buf, FALSE);
+ valp = valblock + keylen + 1;
+
+ ot_ordered_hash_replace_key_take (ohash, valblock, valp);
+}
--- /dev/null
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __OT_ORDERED_HASH_H__
+#define __OT_ORDERED_HASH_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+ GPtrArray *order;
+ GHashTable *table;
+} OtOrderedHash;
+
+OtOrderedHash *ot_ordered_hash_new (void);
+void ot_ordered_hash_free (OtOrderedHash *ohash);
+void ot_ordered_hash_cleanup (void *loc);
+void ot_ordered_hash_replace_key_take (OtOrderedHash *ohash,
+ char *key,
+ const char *value);
+void ot_ordered_hash_replace_key (OtOrderedHash *ohash,
+ const char *key,
+ const char *val);
+
+
+G_END_DECLS
+
+#endif /* __OT_ORDERED_HASH_H__ */
#include "ostree-mount-util.h"
-static void
-parse_ostree_cmdline (char **out_osname,
- char **out_tree)
+static char *
+parse_ostree_cmdline (void)
{
FILE *f = fopen("/proc/cmdline", "r");
char *cmdline = NULL;
const char *iter;
+ char *ret = NULL;
size_t len;
+
if (!f)
- return;
+ return NULL;
if (getline (&cmdline, &len, f) < 0)
- return;
+ return NULL;
if (cmdline[len-1] == '\n')
cmdline[len-1] = '\0';
next_nonspc += 1;
if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0)
{
- const char *slash = strchr (iter, '/');
- if (slash)
- {
- const char *start = iter + strlen ("ostree=");
- *out_osname = strndup (start, slash - start);
- if (next)
- *out_tree = strndup (slash + 1, next - slash - 1);
- else
- *out_tree = strdup (slash + 1);
- break;
- }
+ const char *start = iter + strlen ("ostree=");
+ if (next)
+ ret = strndup (start, next - start);
+ else
+ ret = strdup (start);
+ break;
}
iter = next_nonspc;
}
free (cmdline);
+ return ret;
}
int
main(int argc, char *argv[])
{
- const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
- const char *ostree_bind_mounts[] = { "/var", NULL };
const char *readonly_bind_mounts[] = { "/usr", NULL };
const char *root_mountpoint = NULL;
- char *ostree_osname = NULL;
char *ostree_target = NULL;
- char ostree_target_path[PATH_MAX];
char *deploy_path = NULL;
char srcpath[PATH_MAX];
char destpath[PATH_MAX];
struct stat stbuf;
- size_t len;
int i;
if (argc < 2)
}
root_mountpoint = argv[1];
-
- parse_ostree_cmdline (&ostree_osname, &ostree_target);
-
- if (!ostree_osname)
+ if (strcmp (root_mountpoint, "/sysroot") != 0)
{
- fprintf (stderr, "No OSTree target; expected ostree=OSNAME/TREENAME\n");
+ fprintf (stderr, "ostree-prepare-root: Expected /sysroot\n");
exit (1);
}
- /* Work-around for a kernel bug: for some reason the kernel
- * refuses switching root if any file systems are mounted
- * MS_SHARED. Hence remount them MS_PRIVATE here as a
- * work-around.
- *
- * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
- if (mount (NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+ ostree_target = parse_ostree_cmdline ();
+ if (!ostree_target)
{
- perrorv ("Failed to make \"/\" private mount: %m");
+ fprintf (stderr, "No OSTree target; expected ostree=/ostree/boot.N/...\n");
exit (1);
}
- snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s/%s",
- root_mountpoint, ostree_osname, ostree_target);
+ snprintf (destpath, sizeof(destpath), "%s/%s", root_mountpoint, ostree_target);
fprintf (stderr, "Examining %s\n", destpath);
if (lstat (destpath, &stbuf) < 0)
{
fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath);
exit (1);
}
- if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
+ deploy_path = realpath (destpath, NULL);
+ if (deploy_path == NULL)
{
- perrorv ("readlink(%s) failed: ", destpath);
+ perrorv ("realpath(%s) failed: ", destpath);
exit (1);
}
- len = strlen (ostree_target_path);
- if (ostree_target_path[len-1] == '/')
- ostree_target_path[len-1] = '\0';
- fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
- (void) asprintf (&deploy_path, "%s/ostree/deploy/%s/%s", root_mountpoint,
- ostree_osname, ostree_target_path);
+ fprintf (stderr, "Resolved OSTree target to: %s\n", deploy_path);
+ /* Work-around for a kernel bug: for some reason the kernel
+ * refuses switching root if any file systems are mounted
+ * MS_SHARED. Hence remount them MS_PRIVATE here as a
+ * work-around.
+ *
+ * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
+ if (mount (NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
+ {
+ perrorv ("Failed to make \"/\" private mount: %m");
+ exit (1);
+ }
+
/* Make deploy_path a bind mount, so we can move it later */
if (mount (deploy_path, deploy_path, NULL, MS_BIND, NULL) < 0)
{
exit (1);
}
- snprintf (srcpath, sizeof(srcpath), "%s-etc", deploy_path);
- snprintf (destpath, sizeof(destpath), "%s/etc", deploy_path);
- if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
+ snprintf (srcpath, sizeof(srcpath), "%s/../../var", deploy_path);
+ snprintf (destpath, sizeof(destpath), "%s/var", deploy_path);
+ if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
{
- perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
+ perrorv ("failed to bind mount %s to %s", srcpath, destpath);
exit (1);
}
- for (i = 0; toproot_bind_mounts[i] != NULL; i++)
- {
- snprintf (srcpath, sizeof(srcpath), "%s%s", root_mountpoint, toproot_bind_mounts[i]);
- snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, toproot_bind_mounts[i]);
- /* Only do these bind mounts if the target exists and is a real directory,
- * not a symbolic link.
- */
- if (lstat (destpath, &stbuf) == 0 && S_ISDIR(stbuf.st_mode))
- {
- if (mount (srcpath, destpath, NULL, MS_BIND & ~MS_RDONLY, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:toproot) %s to %s", toproot_bind_mounts[i], destpath);
- exit (1);
- }
- }
- }
-
- for (i = 0; ostree_bind_mounts[i] != NULL; i++)
- {
- snprintf (srcpath, sizeof(srcpath), "%s/ostree/deploy/%s%s", root_mountpoint,
- ostree_osname, ostree_bind_mounts[i]);
- snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, ostree_bind_mounts[i]);
- if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:bind) %s to %s", srcpath, destpath);
- exit (1);
- }
- }
-
for (i = 0; readonly_bind_mounts[i] != NULL; i++)
{
snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, readonly_bind_mounts[i]);
+++ /dev/null
-/* -*- c-file-style: "gnu" -*-
- * Switch to new root directory and start init.
- *
- * Copyright 2011,2012 Colin Walters <walters@verbum.org>
- *
- * Based on code from util-linux/sys-utils/switch_root.c,
- * Copyright 2002-2009 Red Hat, Inc. All rights reserved.
- * Authors:
- * Peter Jones <pjones@redhat.com>
- * Jeremy Katz <katzj@redhat.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-#define _GNU_SOURCE
-#include <sys/mount.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/param.h>
-#include <fcntl.h>
-#include <assert.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <ctype.h>
-#include <dirent.h>
-
-#include "ostree-mount-util.h"
-
-/* remove all files/directories below dirName -- don't cross mountpoints */
-static int
-recursive_remove (int fd)
-{
- struct stat rb;
- DIR *dir;
- int rc = -1;
- int dfd;
-
- if (!(dir = fdopendir (fd)))
- {
- perrorv ("failed to open directory");
- goto done;
- }
-
- /* fdopendir() precludes us from continuing to use the input fd */
- dfd = dirfd (dir);
-
- if (fstat(dfd, &rb))
- {
- perrorv("failed to stat directory");
- goto done;
- }
-
- while (1)
- {
- struct dirent *d;
-
- errno = 0;
- if (!(d = readdir (dir)))
- {
- if (errno)
- {
- perrorv ("failed to read directory");
- goto done;
- }
- break; /* end of directory */
- }
-
- if (!strcmp (d->d_name, ".") || !strcmp (d->d_name, ".."))
- continue;
-
- if (d->d_type == DT_DIR)
- {
- struct stat sb;
-
- if (fstatat (dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW))
- {
- perrorv ("failed to stat %s", d->d_name);
- continue;
- }
-
- /* remove subdirectories if device is same as dir */
- if (sb.st_dev == rb.st_dev)
- {
- int cfd;
-
- cfd = openat (dfd, d->d_name, O_RDONLY);
- if (cfd >= 0)
- {
- recursive_remove (cfd);
- close (cfd);
- }
- }
- else
- {
- continue;
- }
- }
-
- if (unlinkat (dfd, d->d_name,
- d->d_type == DT_DIR ? AT_REMOVEDIR : 0))
- perrorv ("failed to unlink %s", d->d_name);
- }
-
- rc = 0; /* success */
-
- done:
- if (dir)
- closedir (dir);
- return rc;
-}
-
-int
-main(int argc, char *argv[])
-{
- const char *initramfs_move_mounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
- const char *toproot_bind_mounts[] = { "/home", "/root", "/tmp", NULL };
- const char *ostree_bind_mounts[] = { "/var", NULL };
- const char *readonly_bind_mounts[] = { "/usr", NULL };
- const char *root_mountpoint = NULL;
- const char *ostree_target = NULL;
- const char *ostree_subinit = NULL;
- const char *p = NULL;
- char *ostree_osname = NULL;
- char ostree_target_path[PATH_MAX];
- char *deploy_path = NULL;
- char srcpath[PATH_MAX];
- char destpath[PATH_MAX];
- struct stat stbuf;
- char **init_argv = NULL;
- size_t len;
- int initramfs_fd;
- int i;
- int before_init_argc = 0;
- pid_t cleanup_pid;
-
- if (argc < 4)
- {
- fprintf (stderr, "usage: ostree-switch-root NEWROOT TARGET INIT [ARGS...]\n");
- exit (1);
- }
-
- before_init_argc++;
- root_mountpoint = argv[1];
- before_init_argc++;
- ostree_target = argv[2];
- before_init_argc++;
- ostree_subinit = argv[3];
- before_init_argc++;
-
- p = strchr (ostree_target, '/');
- if (p == NULL)
- {
- fprintf (stderr, "Malformed OSTree target %s; expected OSNAME/TREENAME\n", ostree_target);
- exit (1);
- }
- ostree_osname = strndup (ostree_target, p - ostree_target);
-
- snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s",
- root_mountpoint, ostree_target);
- if (stat (destpath, &stbuf) < 0)
- {
- perrorv ("Invalid ostree root '%s'", destpath);
- exit (1);
- }
-
- /* Work-around for a kernel bug: for some reason the kernel
- * refuses switching root if any file systems are mounted
- * MS_SHARED. Hence remount them MS_PRIVATE here as a
- * work-around.
- *
- * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
- if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
- {
- perrorv ("mount(/, MS_PRIVATE): ");
- exit (1);
- }
-
- initramfs_fd = open ("/", O_RDONLY);
-
- for (i = 0; initramfs_move_mounts[i] != NULL; i++)
- {
- const char *path = initramfs_move_mounts[i];
- snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s%s", root_mountpoint, ostree_target, path);
- if (mount (path, destpath, NULL, MS_MOVE, NULL) < 0)
- {
- perrorv ("failed to move mount of %s to %s", path, destpath);
- exit (1);
- }
- }
-
- snprintf (destpath, sizeof(destpath), "%s/ostree/deploy/%s", root_mountpoint, ostree_target);
- fprintf (stderr, "Examining %s\n", destpath);
- if (lstat (destpath, &stbuf) < 0)
- {
- perrorv ("Second stat of ostree root '%s' failed: ", destpath);
- exit (1);
- }
- if (!S_ISLNK (stbuf.st_mode))
- {
- fprintf (stderr, "OSTree target is not a symbolic link: %s\n", destpath);
- exit (1);
- }
- if (readlink (destpath, ostree_target_path, PATH_MAX) < 0)
- {
- perrorv ("readlink(%s) failed: ", destpath);
- exit (1);
- }
- len = strlen (ostree_target_path);
- if (ostree_target_path[len-1] == '/')
- ostree_target_path[len-1] = '\0';
- fprintf (stderr, "Resolved OSTree target to: %s\n", ostree_target_path);
- (void) asprintf (&deploy_path, "%s/ostree/deploy/%s/%s", root_mountpoint,
- ostree_osname, ostree_target_path);
-
- /* Make deploy_path a bind mount, so we can move it later */
- if (mount (deploy_path, deploy_path, NULL, MS_BIND, NULL) < 0)
- {
- perrorv ("failed to initial bind mount %s", deploy_path);
- exit (1);
- }
-
- snprintf (destpath, sizeof(destpath), "%s/sysroot", deploy_path);
- if (mount (root_mountpoint, destpath, NULL, MS_BIND, NULL) < 0)
- {
- perrorv ("Failed to bind mount %s to '%s'", root_mountpoint, destpath);
- exit (1);
- }
-
- snprintf (srcpath, sizeof(srcpath), "%s-etc", deploy_path);
- snprintf (destpath, sizeof(destpath), "%s/etc", deploy_path);
- if (mount (srcpath, destpath, NULL, MS_BIND, NULL) < 0)
- {
- perrorv ("Failed to bind mount '%s' to '%s'", srcpath, destpath);
- exit (1);
- }
-
- for (i = 0; toproot_bind_mounts[i] != NULL; i++)
- {
- snprintf (srcpath, sizeof(srcpath), "%s%s", root_mountpoint, toproot_bind_mounts[i]);
- snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, toproot_bind_mounts[i]);
- if (mount (srcpath, destpath, NULL, MS_BIND & ~MS_RDONLY, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:toproot) %s to %s", toproot_bind_mounts[i], destpath);
- exit (1);
- }
- }
-
- for (i = 0; ostree_bind_mounts[i] != NULL; i++)
- {
- snprintf (srcpath, sizeof(srcpath), "%s/ostree/deploy/%s%s", root_mountpoint,
- ostree_osname, ostree_bind_mounts[i]);
- snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, ostree_bind_mounts[i]);
- if (mount (srcpath, destpath, NULL, MS_MGC_VAL|MS_BIND, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:bind) %s to %s", srcpath, destpath);
- exit (1);
- }
- }
-
- for (i = 0; readonly_bind_mounts[i] != NULL; i++)
- {
- snprintf (destpath, sizeof(destpath), "%s%s", deploy_path, readonly_bind_mounts[i]);
- if (mount (destpath, destpath, NULL, MS_BIND, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:readonly) %s", destpath);
- exit (1);
- }
- if (mount (destpath, destpath, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL) < 0)
- {
- perrorv ("failed to bind mount (class:readonly) %s", destpath);
- exit (1);
- }
- }
-
- if (chdir (deploy_path) < 0)
- {
- perrorv ("failed to chdir to subroot (initial)");
- exit (1);
- }
-
- if (mount (deploy_path, "/", NULL, MS_MOVE, NULL) < 0)
- {
- perrorv ("failed to MS_MOVE %s to /", deploy_path);
- exit (1);
- }
-
- if (chroot (".") < 0)
- {
- perrorv ("failed to change root to '%s'", deploy_path);
- exit (1);
- }
-
- if (chdir ("/") < 0)
- {
- perrorv ("failed to chdir to / (after MS_MOVE of /)");
- exit (1);
- }
-
- if (initramfs_fd >= 0)
- {
- cleanup_pid = fork ();
- if (cleanup_pid == 0)
- {
- recursive_remove (initramfs_fd);
- exit (0);
- }
- close (initramfs_fd);
- }
-
- init_argv = malloc (sizeof (char*)*((argc-before_init_argc)+2));
- init_argv[0] = (char*)ostree_subinit;
- for (i = 0; i < argc-before_init_argc; i++)
- init_argv[i+1] = argv[i+before_init_argc];
- init_argv[i+1] = NULL;
-
- fprintf (stderr, "ostree-init: Running real init %s (argc=%d)\n", init_argv[0], argc-before_init_argc);
- fflush (stderr);
- execv (init_argv[0], init_argv);
- perrorv ("Failed to exec init '%s'", init_argv[0]);
- exit (1);
-}
-
test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1)
}
+assert_not_streq () {
+ (! test "$1" = "$2") || (echo 1>&2 "$1 == $2"; exit 1)
+}
+
assert_has_file () {
test -f "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
}
+assert_has_dir () {
+ test -d "$1" || (echo 1>&2 "Couldn't find '$1'"; exit 1)
+}
+
assert_not_has_file () {
if test -f "$1"; then
echo 1>&2 "File '$1' exists"; exit 1
fi
}
+assert_not_has_dir () {
+ if test -d "$1"; then
+ echo 1>&2 "Directory '$1' exists"; exit 1
+ fi
+}
+
assert_file_has_content () {
if ! grep -q -e "$2" "$1"; then
echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1
export OSTREE="ostree --repo=repo"
}
+
+setup_os_repository () {
+ mode=$1
+ shift
+
+ oldpwd=`pwd`
+
+ cd ${test_tmpdir}
+ mkdir testos-repo
+ if test -n "$mode"; then
+ ostree --repo=testos-repo init --mode=${mode}
+ else
+ ostree --repo=testos-repo init
+ fi
+
+ cd ${test_tmpdir}
+ mkdir osdata
+ cd osdata
+ mkdir -p boot usr/bin usr/lib/modules/3.6.0 usr/share usr/etc
+ echo "a kernel" > boot/vmlinuz-3.6.0
+ echo "an initramfs" > boot/initramfs-3.6.0
+ echo "a kernel module" > usr/lib/modules/3.6.0/foofs.ko
+ bootcsum=$(cat boot/vmlinuz-3-6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko | sha256sum | cut -f 1 -d ' ')
+ export bootcsum
+ mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
+ mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
+
+ echo "an executable" > usr/bin/sh
+ echo "some shared data" > usr/share/langs.txt
+ echo "a library" > usr/lib/libfoo.so.0
+ ln -s usr/bin bin
+cat > usr/etc/os-release <<EOF
+NAME=TestOS
+VERSION=42
+ID=testos
+VERSION_ID=42
+PRETTY_NAME="TestOS 42"
+EOF
+ echo "a config file" > usr/etc/aconfigfile
+ mkdir -p usr/etc/NetworkManager
+ echo "a default daemon file" > usr/etc/NetworkManager/nm.conf
+
+ ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+
+ echo "a new executable" > usr/bin/sh
+ ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+
+ ostree --repo=${test_tmpdir}/testos-repo fsck -q
+
+ cd ${test_tmpdir}
+ mkdir sysroot
+ ostree admin --sysroot=sysroot init-fs sysroot
+ ostree admin --sysroot=sysroot os-init testos
+
+ # Stub syslinux configuration
+ mkdir -p sysroot/boot/loader.0
+ ln -s loader.0 sysroot/boot/loader
+ touch sysroot/boot/loader/syslinux.cfg
+ # And a compatibility symlink
+ mkdir -p sysroot/boot/syslinux
+ ln -s ../loader/syslinux.cfg sysroot/boot/syslinux/syslinux.cfg
+}
+
+os_repository_new_commit ()
+{
+ cd ${test_tmpdir}/osdata
+ rm boot/*
+ echo "new: a kernel" > boot/vmlinuz-3.6.0
+ echo "new: an initramfs" > boot/initramfs-3.6.0
+ echo "new: a kernel module" > usr/lib/modules/3.6.0/foofs.ko
+ echo "new: another kernel module" > usr/lib/modules/3.6.0/othermod.ko
+ bootcsum=$(cat boot/vmlinuz-3.6.0 boot/initramfs-3.6.0 usr/lib/modules/3.6.0/foofs.ko usr/lib/modules/3.6.0/othermod.ko | sha256sum | cut -f 1 -d ' ')
+ export bootcsum
+ mv boot/vmlinuz-3.6.0 boot/vmlinuz-3.6.0-${bootcsum}
+ mv boot/initramfs-3.6.0 boot/initramfs-3.6.0-${bootcsum}
+
+ echo "a new default config file" > usr/etc/a-new-default-config-file
+ mkdir -p usr/etc/new-default-dir
+ echo "a new default dir and file" > usr/etc/new-default-dir/moo
+
+ ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build"
+ cd ${test_tmpdir}
+}
--- /dev/null
+#!/bin/bash
+#
+# Copyright (C) 2011 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -e
+
+. $(dirname $0)/libtest.sh
+
+echo "1..1"
+
+setup_os_repository "archive-z2"
+
+echo "ok setup"
+
+echo "1..7"
+
+ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
+rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
+export rev
+# This initial deployment gets kicked off with some kernel arguments
+ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime
+
+echo "ok deploy command"
+
+assert_not_has_dir sysroot/boot/loader.0
+assert_has_dir sysroot/boot/loader.1
+assert_has_dir sysroot/ostree/boot.1.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* quiet'
+assert_file_has_content sysroot/boot/ostree/testos-${bootcsum}/vmlinuz-3.6.0 'a kernel'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/boot.1/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
+
+echo "ok layout"
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+# Need a new bootversion, sine we now have two deployments
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+assert_has_dir sysroot/ostree/boot.0.1
+assert_not_has_dir sysroot/ostree/boot.0.0
+assert_not_has_dir sysroot/ostree/boot.1.0
+assert_not_has_dir sysroot/ostree/boot.1.1
+# Ensure we propagated kernel arguments from previous deployment
+assert_file_has_content sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf 'options.* root=LABEL=MOO'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/boot.0/testos/${bootcsum}/0/etc/os-release 'NAME=TestOS'
+
+echo "ok second deploy"
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+# Keep the same bootversion
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+# But swap subbootversion
+assert_has_dir sysroot/ostree/boot.0.0
+assert_not_has_dir sysroot/ostree/boot.0.1
+
+echo "ok third deploy (swap)"
+
+ostree admin --sysroot=sysroot deploy --os=otheros testos/buildmaster/x86_64-runtime
+assert_not_has_dir sysroot/boot/loader.0
+assert_has_dir sysroot/boot/loader.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_has_file sysroot/boot/loader/entries/ostree-otheros-${rev}-0.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.1/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/otheros/deploy/${rev}.0/etc/os-release 'NAME=TestOS'
+
+echo "ok independent deploy"
+
+ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
+assert_has_dir sysroot/boot/loader.0
+assert_not_has_dir sysroot/boot/loader.1
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-0.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.2/etc/os-release 'NAME=TestOS'
+assert_has_file sysroot/boot/loader/entries/ostree-testos-${rev}-2.conf
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
+
+echo "ok fourth deploy (retain)"
+
+echo "a new local config file" > sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-config-file
+rm sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
+ln -s /ENOENT sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/a-new-broken-symlink
+ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime
+linktarget=$(readlink sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-broken-symlink)
+test "${linktarget}" = /ENOENT
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/os-release 'NAME=TestOS'
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/a-new-config-file 'a new local config file'
+assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.4/etc/aconfigfile
+
+echo "ok deploy with modified /etc"
+
+os_repository_new_commit
+ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
+newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos:testos/buildmaster/x86_64-runtime)
+export newrev
+assert_not_streq ${rev} ${newrev}
+
+ostree admin --sysroot=sysroot deploy --os=testos testos:testos/buildmaster/x86_64-runtime
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
+# New files in /usr/etc
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/a-new-default-config-file "a new default config file"
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/new-default-dir/moo "a new default dir and file"
+# And persist /etc changes from before
+assert_not_has_file sysroot/ostree/deploy/testos/deploy/${rev}.3/etc/aconfigfile
+
+echo "ok upgrade bare"
+
+os_repository_new_commit
+ostree --repo=sysroot/ostree/repo remote add testos file://$(pwd)/testos-repo testos/buildmaster/x86_64-runtime
+ostree admin --sysroot=sysroot upgrade --os=testos
+rev=${newrev}
+newrev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
+assert_not_streq ${rev} ${newrev}
+assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
+
+echo "ok upgrade"